<commit_before>/**
 * @memberOf Jymfony.Component.DependencyInjection
 */
class Definition {
    /**
     * Constructor.
     *
     * @param {string} [class_]
     * @param {Array} [args = []]
     */
    __construct(class_ = undefined, args = []) {
        /**
         * @type {string|undefined}
         *
         * @private
         */
        this._class = undefined;

        /**
         * @type {Array}
         *
         * @private
         */
        this._arguments = args;

        /**
         * @type {string[]|undefined}
         *
         * @private
         */
        this._module = undefined;

        /**
         * @type {string|Array|undefined}
         *
         * @private
         */
        this._factory = undefined;

        /**
         * @type {boolean}
         *
         * @private
         */
        this._shared = true;

        /**
         * @type {boolean}
         *
         * @private
         */
        this._deprecated = false;

        /**
         * @type {string}
         *
         * @private
         */
        this._deprecationTemplate = 'The "%service_id%" service is deprecated. You should stop using it';

        /**
         * @type {Object}
         *
         * @private
         */
        this._properties = {};

        /**
         * @type {Array}
         *
         * @private
         */
        this._calls = [];

        /**
         * @type {string|Array|Function|undefined}
         *
         * @private
         */
        this._configurator = undefined;

        /**
         * @type {Object.<string, string>}
         *
         * @private
         */
        this._tags = {};

        /**
         * @type {boolean}
         *
         * @private
         */
        this._public = false;

        /**
         * @type {boolean}
         *
         * @private
         */
        this._synthetic = false;

        /**
         * @type {boolean}
         *
         * @private
         */
        this._abstract = false;

        /**
         * @type {boolean}
         *
         * @private
         */
        this._lazy = false;

        /**
         * @type {*}
         *
         * @private
         */
        this._decoratedService = undefined;

        /**
         * @type {Object}
         *
         * @private
         */
        this._changes = {};

        if (undefined !== class_) {
            this.setClass(class_);
        }
    }

    /**
     * Sets the changes.
     *
     * @param {Object} changes
     *
     * @returns {Jymfony.Component.DependencyInjection.Definition}
     */
    setChanges(changes) {
        this._changes = Object.assign({}, changes);

        return this;
    }

    /**
     * Returns all changes tracked for the Definition object.
     *
     * @returns {Object}
     */
    getChanges() {
        return Object.assign({}, this._changes);
    }

    /**
     * Sets the service factory.
     *
     * @param {string|Array} factory
     *
     * @returns {Jymfony.Component.DependencyInjection.Definition}
     */
    setFactory(factory) {
        this._changes['factory'] = true;

        if (isString(factory) && -1 !== factory.indexOf('#')) {
            factory = factory.split('#', 2);
        }

        this._factory = factory;
        return this;
    }

    /**
     * Gets the current factory.
     *
     * @returns {string|Array|undefined}
     */
    getFactory() {
        return this._factory;
    }

    /**
     * Sets the service that this service is decorating.
     *
     * @param {string} id
     * @param {string} [renamedId]
     * @param {int} [priority = 0]
     *
     * @returns {Jymfony.Component.DependencyInjection.Definition}
     */
    setDecoratedService(id, renamedId = undefined, priority = 0) {
        if (renamedId && id === renamedId) {
            throw new InvalidArgumentException('The decorated service inner name for "' + id + '" must be different than the service name');
        }

        this._changes['decorated_service'] = true;

        if (! id) {
            this._decoratedService = undefined;
        } else {
            this._decoratedService = [ id, renamedId, priority ];
        }

        return this;
    }

    /**
     * Gets the decorated service definition (id, inner id, priority).
     *
     * @returns {Array}
     */
    getDecoratedService() {
        if (! this._decoratedService) {
            return this._decoratedService;
        }

        return [ ...this._decoratedService ];
    }

    /**
     * Sets the service class.
     *
     * @param {string|Function} className
     *
     * @returns {Jymfony.Component.DependencyInjection.Definition}
     */
    setClass(className) {
        if (isFunction(className)) {
            try {
                className = (new ReflectionClass(className)).name;
            } catch (e) { }
        }

        this._changes['class'] = true;
        this._class = className;

        return this;
    }

    /**
     * Gets service class name.
     *
     * @returns {string}
     */
    getClass() {
        return this._class;
    }

    /**
     * Sets the arguments to pass to the service constructor/factory.
     *
     * @param {Array} args
     *
     * @returns {Jymfony.Component.DependencyInjection.Definition}
     */
    setArguments(args) {
        this._arguments = args;

        return this;
    }

    /**
     * Adds an argument to the list.
     *
     * @param {*} argument
     *
     * @returns {Jymfony.Component.DependencyInjection.Definition}
     */
    addArgument(argument) {
        this._arguments.push(argument);

        return this;
    }

    /**
     * Replaces an argument.
     *
     * @param {int} index
     * @param {*} argument
     *
     * @returns {Jymfony.Component.DependencyInjection.Definition}
     */
    replaceArgument(index, argument) {
        if (0 > index || index >= this._arguments.length) {
            throw new InvalidArgumentException('Index is not in the range [0, ' + this._arguments.length.toString() + ']');
        }

        this._arguments[index] = argument;

        return this;
    }

    /**
     * Gets the argument list.
     *
     * @returns {Array}
     */
    getArguments() {
        return __jymfony.deepClone(this._arguments);
    }

    /**
     * Gets a single argument.
     *
     * @param {int} index
     *
     * @returns {*}
     */
    getArgument(index) {
        if (0 > index || index >= this._arguments.length) {
            throw new InvalidArgumentException('Index is not in the range [0, ' + this._arguments.length.toString() + ']');
        }

        return this._arguments[index];
    }

    /**
     * Defines properties to set at service creation.
     *
     * @param {Object} properties
     *
     * @returns {Jymfony.Component.DependencyInjection.Definition}
     */
    setProperties(properties) {
        this._properties = properties;

        return this;
    }

    /**
     * Sets a property to set to the service.
     *
     * @param {string} property
     * @param {*} value
     *
     * @returns {Jymfony.Component.DependencyInjection.Definition}
     */
    addProperty(property, value) {
        this._properties[property] = value;

        return this;
    }

    /**
     * Gets the properties to set on the service.
     *
     * @returns {Object}
     */
    getProperties() {
        return __jymfony.deepClone(this._properties);
    }

    /**
     * Sets the methods to call after service construction.
     *
     * @param {Array} [calls = []]
     *
     * @returns {Jymfony.Component.DependencyInjection.Definition}
     */
    setMethodCalls(calls = []) {
        this._calls = [];
        for (const call of calls) {
            this.addMethodCall(call[0], (call[1] || []));
        }

        return this;
    }

    /**
     * Adds a method to call.
     *
     * @param {string} method
     * @param {Array} [args = []]
     *
     * @returns {Jymfony.Component.DependencyInjection.Definition}
     */
    addMethodCall(method, args = []) {
        if (! method) {
            throw new InvalidArgumentException('Method name cannot be empty');
        }

        this._calls.push([ method, args ]);

        return this;
    }

    /**
     * Removes a method to call after service initialization.
     *
     * @param {string} method
     *
     * @returns {Jymfony.Component.DependencyInjection.Definition}
     */
    removeMethodCall(method) {
        for (let i = 0; i < this._calls.length; i++) {
            const call = this._calls[i];
            if (call[0] === method) {
                this._calls.splice(i, 1);
                break;
            }
        }

        return this;
    }

    /**
     * Checks if the definition has a given method call.
     *
     * @param {string} method
     *
     * @returns {boolean}
     */
    hasMethodCall(method) {
        for (const call of this._calls) {
            if (call[0] === method) {
                return true;
            }
        }

        return false;
    }

    /**
     * Gets the methods to call.
     *
     * @returns {Array}
     */
    getMethodCalls() {
        return __jymfony.deepClone(this._calls);
    }

    /**
     * Sets the service tags.
     *
     * @param {Object} tags
     *
     * @returns {Jymfony.Component.DependencyInjection.Definition}
     */
    setTags(tags) {
        this._tags = tags;

        return this;
    }

    /**
     * Gets all tags.
     *
     * @returns {Object}
     */
    getTags() {
        return __jymfony.deepClone(this._tags);
    }

    /**
     * Gets a tag by name.
     *
     * @param {string} name
     *
     * @returns {Object}
     */
    getTag(name) {
        if (! this._tags[name]) {
            return {};
        }

        return __jymfony.deepClone(this._tags[name]);
    }

    /**
     * Adds a tag.
     *
     * @param {string} name
     * @param {Object} attributes
     *
     * @returns {Jymfony.Component.DependencyInjection.Definition}
     */
    addTag(name, attributes = {}) {
        this._tags[name] = this._tags[name] || [];
        this._tags[name].push(attributes);

        return this;
    }

    /**
     * Checks if a tag is present.
     *
     * @param {string} name
     *
     * @returns {boolean}
     */
    hasTag(name) {
        return !! this._tags[name];
    }

    /**
     * Clears all the tags for a given name.
     *
     * @param {string} name
     *
     * @returns {Jymfony.Component.DependencyInjection.Definition}
     */
    clearTag(name) {
        delete this._tags[name];

        return this;
    }

    /**
     * Clears all the tags.
     *
     * @returns {Jymfony.Component.DependencyInjection.Definition}
     */
    clearTags() {
        this._tags = {};

        return this;
    }

    /**
     * Use a module require as a factory for this service (with optional property).
     * The "new" operator is used only if a property is defined, otherwise
     * the result of the require call is used as service.
     *
     * @param {string} module The module to be required.
     * @param {string} [property] The property to be used as service in module.
     *
     * @returns {Jymfony.Component.DependencyInjection.Definition}
     */
    setModule(module, property = undefined) {
        this._changes['module'] = true;
        this._module = [ module, property ];

        return this;
    }

    /**
     * Gets the module to be used as service.
     *
     * @returns {string[]}
     */
    getModule() {
        return this._module;
    }

    /**
     * Sets if this service must be shared.
     *
     * @param {boolean} shared
     *
     * @returns {Jymfony.Component.DependencyInjection.Definition}
     */
    setShared(shared) {
        this._changes['shared'] = true;
        this._shared = !! shared;

        return this;
    }

    /**
     * Checks if this service is shared.
     *
     * @returns {boolean}
     */
    isShared() {
        return this._shared;
    }

    /**
     * Sets if this service is public (can be retrieved by Container#get).
     *
     * @param {boolean} _public
     *
     * @returns {Jymfony.Component.DependencyInjection.Definition}
     */
    setPublic(_public) {
        this._changes['public'] = true;
        this._public = !! _public;

        return this;
    }

    /**
     * Checks if this service should be public.
     *
     * @returns {boolean}
     */
    isPublic() {
        return this._public;
    }

    /**
     * Sets the lazy flag for this service.
     *
     * @param {boolean} lazy
     *
     * @returns {Jymfony.Component.DependencyInjection.Definition}
     */
    setLazy(lazy) {
        this._changes['lazy'] = true;
        this._lazy = !! lazy;

        return this;
    }

    /**
     * Is this service lazy?
     *
     * @returns {boolean}
     */
    isLazy() {
        return this._lazy;
    }

    /**
     * Sets if this is a synthetic service (cannot be constructed,
     * but must be injected).
     *
     * @param {boolean} synthetic
     *
     * @returns {Jymfony.Component.DependencyInjection.Definition}
     */
    setSynthetic(synthetic) {
        this._synthetic = !! synthetic;

        return this;
    }

    /**
     * Checks if this service is synthetic.
     *
     * @returns {boolean}
     */
    isSynthetic() {
        return this._synthetic;
    }

    /**
     * Sets the abstract flag for this service
     * Abstract services serves as templates for other ones.
     *
     * @param {boolean} abstract
     *
     * @returns {Jymfony.Component.DependencyInjection.Definition}
     */
    setAbstract(abstract) {
        this._abstract = !! abstract;

        return this;
    }

    /**
     * Checks if this is an abstract service.
     *
     * @returns {boolean}
     */
    isAbstract() {
        return this._abstract;
    }

    /**
     * Set the deprecated flag and message for this service
     * If marked as deprecated, a deprecation warning will be triggered
     * during its initialization. The deprecation message is defined by
     * the template parameter and could contain '%service_id%' that will
     * be replaced by the real service id.
     *
     * @param {boolean} [status = true]
     * @param {string} [template]
     *
     * @returns {Jymfony.Component.DependencyInjection.Definition}
     */
    setDeprecated(status = true, template = undefined) {
        if (template) {
            if (/[\r\n]|\*\//.test(template)) {
                throw new InvalidArgumentException('Invalid characters found in deprecation notice template');
            }

            this._deprecationTemplate = template;
        }

        this._changes['deprecated'] = true;
        this._deprecated = !! status;

        return this;
    }

    /**
     * Checks whether this service is deprecated.
     *
     * @returns {boolean}
     */
    isDeprecated() {
        return this._deprecated;
    }

    /**
     * Build the deprecation message for a given service id.
     *
     * @param {string} id
     *
     * @returns {string}
     */
    getDeprecationMessage(id) {
        return this._deprecationTemplate.replace(/%service_id%/g, id);
    }

    /**
     * Sets a configurator to be called after the service is initialized.
     *
     * @param {string|Array|Function} configurator
     *
     * @returns {Jymfony.Component.DependencyInjection.Definition}
     */
    setConfigurator(configurator) {
        this._changes['configurator'] = true;
        if (isString(configurator) && -1 !== configurator.indexOf('#')) {
            configurator = configurator.split('#', 2);
        }

        this._configurator = configurator;

        return this;
    }

    /**
     * Gets the configurator for this service.
     *
     * @returns {string|Array|Function|undefined}
     */
    getConfigurator() {
        return this._configurator;
    }
}

module.exports = Definition;
<commit_msg>[Routing] add url generator<commit_after>/**
 * @memberOf Jymfony.Component.DependencyInjection
 */
class Definition {
    /**
     * Constructor.
     *
     * @param {string} [class_]
     * @param {Array} [args = []]
     */
    __construct(class_ = undefined, args = []) {
        /**
         * @type {string|undefined}
         *
         * @private
         */
        this._class = undefined;

        /**
         * @type {Array}
         *
         * @private
         */
        this._arguments = args;

        /**
         * @type {string[]|undefined}
         *
         * @private
         */
        this._module = undefined;

        /**
         * @type {string|Array|undefined}
         *
         * @private
         */
        this._factory = undefined;

        /**
         * @type {boolean}
         *
         * @private
         */
        this._shared = true;

        /**
         * @type {boolean}
         *
         * @private
         */
        this._deprecated = false;

        /**
         * @type {string}
         *
         * @private
         */
        this._deprecationTemplate = 'The "%service_id%" service is deprecated. You should stop using it';

        /**
         * @type {Object}
         *
         * @private
         */
        this._properties = {};

        /**
         * @type {Array}
         *
         * @private
         */
        this._calls = [];

        /**
         * @type {string|Array|Function|undefined}
         *
         * @private
         */
        this._configurator = undefined;

        /**
         * @type {Object.<string, string>}
         *
         * @private
         */
        this._tags = {};

        /**
         * @type {boolean}
         *
         * @private
         */
        this._public = false;

        /**
         * @type {boolean}
         *
         * @private
         */
        this._synthetic = false;

        /**
         * @type {boolean}
         *
         * @private
         */
        this._abstract = false;

        /**
         * @type {boolean}
         *
         * @private
         */
        this._lazy = false;

        /**
         * @type {*}
         *
         * @private
         */
        this._decoratedService = undefined;

        /**
         * @type {Object}
         *
         * @private
         */
        this._changes = {};

        if (undefined !== class_) {
            this.setClass(class_);
        }
    }

    /**
     * Sets the changes.
     *
     * @param {Object} changes
     *
     * @returns {Jymfony.Component.DependencyInjection.Definition}
     */
    setChanges(changes) {
        this._changes = Object.assign({}, changes);

        return this;
    }

    /**
     * Returns all changes tracked for the Definition object.
     *
     * @returns {Object}
     */
    getChanges() {
        return Object.assign({}, this._changes);
    }

    /**
     * Sets the service factory.
     *
     * @param {string|Array} factory
     *
     * @returns {Jymfony.Component.DependencyInjection.Definition}
     */
    setFactory(factory) {
        this._changes['factory'] = true;

        if (isString(factory) && -1 !== factory.indexOf('#')) {
            factory = factory.split('#', 2);
        }

        this._factory = factory;
        return this;
    }

    /**
     * Gets the current factory.
     *
     * @returns {string|Array|undefined}
     */
    getFactory() {
        return this._factory;
    }

    /**
     * Sets the service that this service is decorating.
     *
     * @param {string} id
     * @param {string} [renamedId]
     * @param {int} [priority = 0]
     *
     * @returns {Jymfony.Component.DependencyInjection.Definition}
     */
    setDecoratedService(id, renamedId = undefined, priority = 0) {
        if (renamedId && id === renamedId) {
            throw new InvalidArgumentException('The decorated service inner name for "' + id + '" must be different than the service name');
        }

        this._changes['decorated_service'] = true;

        if (! id) {
            this._decoratedService = undefined;
        } else {
            this._decoratedService = [ id, renamedId, priority ];
        }

        return this;
    }

    /**
     * Gets the decorated service definition (id, inner id, priority).
     *
     * @returns {Array}
     */
    getDecoratedService() {
        if (! this._decoratedService) {
            return this._decoratedService;
        }

        return [ ...this._decoratedService ];
    }

    /**
     * Sets the service class.
     *
     * @param {string|Function} className
     *
     * @returns {Jymfony.Component.DependencyInjection.Definition}
     */
    setClass(className) {
        if (isFunction(className)) {
            try {
                className = (new ReflectionClass(className)).name;
            } catch (e) { }
        }

        this._changes['class'] = true;
        this._class = className;

        return this;
    }

    /**
     * Gets service class name.
     *
     * @returns {string}
     */
    getClass() {
        return this._class;
    }

    /**
     * Sets the arguments to pass to the service constructor/factory.
     *
     * @param {Array} args
     *
     * @returns {Jymfony.Component.DependencyInjection.Definition}
     */
    setArguments(args) {
        this._arguments = args;

        return this;
    }

    /**
     * Adds an argument to the list.
     *
     * @param {*} argument
     *
     * @returns {Jymfony.Component.DependencyInjection.Definition}
     */
    addArgument(argument) {
        this._arguments.push(argument);

        return this;
    }

    /**
     * Replaces an argument.
     *
     * @param {int} index
     * @param {*} argument
     *
     * @returns {Jymfony.Component.DependencyInjection.Definition}
     */
    replaceArgument(index, argument) {
        if (0 > index || index >= this._arguments.length) {
            throw new InvalidArgumentException('Index is not in the range [0, ' + (this._arguments.length - 1).toString() + ']');
        }

        this._arguments[index] = argument;

        return this;
    }

    /**
     * Gets the argument list.
     *
     * @returns {Array}
     */
    getArguments() {
        return __jymfony.deepClone(this._arguments);
    }

    /**
     * Gets a single argument.
     *
     * @param {int} index
     *
     * @returns {*}
     */
    getArgument(index) {
        if (0 > index || index >= this._arguments.length) {
            throw new InvalidArgumentException('Index is not in the range [0, ' + (this._arguments.length - 1).toString() + ']');
        }

        return this._arguments[index];
    }

    /**
     * Defines properties to set at service creation.
     *
     * @param {Object} properties
     *
     * @returns {Jymfony.Component.DependencyInjection.Definition}
     */
    setProperties(properties) {
        this._properties = properties;

        return this;
    }

    /**
     * Sets a property to set to the service.
     *
     * @param {string} property
     * @param {*} value
     *
     * @returns {Jymfony.Component.DependencyInjection.Definition}
     */
    addProperty(property, value) {
        this._properties[property] = value;

        return this;
    }

    /**
     * Gets the properties to set on the service.
     *
     * @returns {Object}
     */
    getProperties() {
        return __jymfony.deepClone(this._properties);
    }

    /**
     * Sets the methods to call after service construction.
     *
     * @param {Array} [calls = []]
     *
     * @returns {Jymfony.Component.DependencyInjection.Definition}
     */
    setMethodCalls(calls = []) {
        this._calls = [];
        for (const call of calls) {
            this.addMethodCall(call[0], (call[1] || []));
        }

        return this;
    }

    /**
     * Adds a method to call.
     *
     * @param {string} method
     * @param {Array} [args = []]
     *
     * @returns {Jymfony.Component.DependencyInjection.Definition}
     */
    addMethodCall(method, args = []) {
        if (! method) {
            throw new InvalidArgumentException('Method name cannot be empty');
        }

        this._calls.push([ method, args ]);

        return this;
    }

    /**
     * Removes a method to call after service initialization.
     *
     * @param {string} method
     *
     * @returns {Jymfony.Component.DependencyInjection.Definition}
     */
    removeMethodCall(method) {
        for (let i = 0; i < this._calls.length; i++) {
            const call = this._calls[i];
            if (call[0] === method) {
                this._calls.splice(i, 1);
                break;
            }
        }

        return this;
    }

    /**
     * Checks if the definition has a given method call.
     *
     * @param {string} method
     *
     * @returns {boolean}
     */
    hasMethodCall(method) {
        for (const call of this._calls) {
            if (call[0] === method) {
                return true;
            }
        }

        return false;
    }

    /**
     * Gets the methods to call.
     *
     * @returns {Array}
     */
    getMethodCalls() {
        return __jymfony.deepClone(this._calls);
    }

    /**
     * Sets the service tags.
     *
     * @param {Object} tags
     *
     * @returns {Jymfony.Component.DependencyInjection.Definition}
     */
    setTags(tags) {
        this._tags = tags;

        return this;
    }

    /**
     * Gets all tags.
     *
     * @returns {Object}
     */
    getTags() {
        return __jymfony.deepClone(this._tags);
    }

    /**
     * Gets a tag by name.
     *
     * @param {string} name
     *
     * @returns {Object}
     */
    getTag(name) {
        if (! this._tags[name]) {
            return {};
        }

        return __jymfony.deepClone(this._tags[name]);
    }

    /**
     * Adds a tag.
     *
     * @param {string} name
     * @param {Object} attributes
     *
     * @returns {Jymfony.Component.DependencyInjection.Definition}
     */
    addTag(name, attributes = {}) {
        this._tags[name] = this._tags[name] || [];
        this._tags[name].push(attributes);

        return this;
    }

    /**
     * Checks if a tag is present.
     *
     * @param {string} name
     *
     * @returns {boolean}
     */
    hasTag(name) {
        return !! this._tags[name];
    }

    /**
     * Clears all the tags for a given name.
     *
     * @param {string} name
     *
     * @returns {Jymfony.Component.DependencyInjection.Definition}
     */
    clearTag(name) {
        delete this._tags[name];

        return this;
    }

    /**
     * Clears all the tags.
     *
     * @returns {Jymfony.Component.DependencyInjection.Definition}
     */
    clearTags() {
        this._tags = {};

        return this;
    }

    /**
     * Use a module require as a factory for this service (with optional property).
     * The "new" operator is used only if a property is defined, otherwise
     * the result of the require call is used as service.
     *
     * @param {string} module The module to be required.
     * @param {string} [property] The property to be used as service in module.
     *
     * @returns {Jymfony.Component.DependencyInjection.Definition}
     */
    setModule(module, property = undefined) {
        this._changes['module'] = true;
        this._module = [ module, property ];

        return this;
    }

    /**
     * Gets the module to be used as service.
     *
     * @returns {string[]}
     */
    getModule() {
        return this._module;
    }

    /**
     * Sets if this service must be shared.
     *
     * @param {boolean} shared
     *
     * @returns {Jymfony.Component.DependencyInjection.Definition}
     */
    setShared(shared) {
        this._changes['shared'] = true;
        this._shared = !! shared;

        return this;
    }

    /**
     * Checks if this service is shared.
     *
     * @returns {boolean}
     */
    isShared() {
        return this._shared;
    }

    /**
     * Sets if this service is public (can be retrieved by Container#get).
     *
     * @param {boolean} _public
     *
     * @returns {Jymfony.Component.DependencyInjection.Definition}
     */
    setPublic(_public) {
        this._changes['public'] = true;
        this._public = !! _public;

        return this;
    }

    /**
     * Checks if this service should be public.
     *
     * @returns {boolean}
     */
    isPublic() {
        return this._public;
    }

    /**
     * Sets the lazy flag for this service.
     *
     * @param {boolean} lazy
     *
     * @returns {Jymfony.Component.DependencyInjection.Definition}
     */
    setLazy(lazy) {
        this._changes['lazy'] = true;
        this._lazy = !! lazy;

        return this;
    }

    /**
     * Is this service lazy?
     *
     * @returns {boolean}
     */
    isLazy() {
        return this._lazy;
    }

    /**
     * Sets if this is a synthetic service (cannot be constructed,
     * but must be injected).
     *
     * @param {boolean} synthetic
     *
     * @returns {Jymfony.Component.DependencyInjection.Definition}
     */
    setSynthetic(synthetic) {
        this._synthetic = !! synthetic;

        return this;
    }

    /**
     * Checks if this service is synthetic.
     *
     * @returns {boolean}
     */
    isSynthetic() {
        return this._synthetic;
    }

    /**
     * Sets the abstract flag for this service
     * Abstract services serves as templates for other ones.
     *
     * @param {boolean} abstract
     *
     * @returns {Jymfony.Component.DependencyInjection.Definition}
     */
    setAbstract(abstract) {
        this._abstract = !! abstract;

        return this;
    }

    /**
     * Checks if this is an abstract service.
     *
     * @returns {boolean}
     */
    isAbstract() {
        return this._abstract;
    }

    /**
     * Set the deprecated flag and message for this service
     * If marked as deprecated, a deprecation warning will be triggered
     * during its initialization. The deprecation message is defined by
     * the template parameter and could contain '%service_id%' that will
     * be replaced by the real service id.
     *
     * @param {boolean} [status = true]
     * @param {string} [template]
     *
     * @returns {Jymfony.Component.DependencyInjection.Definition}
     */
    setDeprecated(status = true, template = undefined) {
        if (template) {
            if (/[\r\n]|\*\//.test(template)) {
                throw new InvalidArgumentException('Invalid characters found in deprecation notice template');
            }

            this._deprecationTemplate = template;
        }

        this._changes['deprecated'] = true;
        this._deprecated = !! status;

        return this;
    }

    /**
     * Checks whether this service is deprecated.
     *
     * @returns {boolean}
     */
    isDeprecated() {
        return this._deprecated;
    }

    /**
     * Build the deprecation message for a given service id.
     *
     * @param {string} id
     *
     * @returns {string}
     */
    getDeprecationMessage(id) {
        return this._deprecationTemplate.replace(/%service_id%/g, id);
    }

    /**
     * Sets a configurator to be called after the service is initialized.
     *
     * @param {string|Array|Function} configurator
     *
     * @returns {Jymfony.Component.DependencyInjection.Definition}
     */
    setConfigurator(configurator) {
        this._changes['configurator'] = true;
        if (isString(configurator) && -1 !== configurator.indexOf('#')) {
            configurator = configurator.split('#', 2);
        }

        this._configurator = configurator;

        return this;
    }

    /**
     * Gets the configurator for this service.
     *
     * @returns {string|Array|Function|undefined}
     */
    getConfigurator() {
        return this._configurator;
    }
}

module.exports = Definition;
<|endoftext|>


<commit_before>const path = require('path')
const Discord = require('discord.js-commando')
const request = require('request-promise')
const config = require('./data/client.json')
const DiscordServer = require('./DiscordServer')
const { Cache } = require('./GlobalCache')
const requestDebug = require('request-debug')
const SettingProvider = require('./commands/SettingProvider')
const Util = require('./Util')
const fs = require('mz/fs')

if (config.loud) requestDebug(request, (type, data) => console.log(`${type} ${data.debugId} : ${data.uri || data.statusCode}`))

/**
 * The main Discord bot class, only one per shard.
 * @class DiscordBot
 */
class DiscordBot {
  constructor () {
    this.initialize()
    this.servers = {}
    this.authorizedOwners = []
    this.patronTransfers = {}
    this.blacklist = {}
  }

  /**
   * Initialize the bot, hook up events, and log in.
   * @memberof DiscordBot
   */
  initialize () {
    this.bot = new Discord.Client({
      apiRequestMethod: config.apiRequestMethod || 'sequential',
      disabledEvents: ['TYPING_START', 'VOICE_STATE_UPDATE', 'PRESENCE_UPDATE', 'MESSAGE_DELETE', 'MESSAGE_UPDATE', 'CHANNEL_PINS_UPDATE', 'MESSAGE_REACTION_ADD', 'MESSAGE_REACTION_REMOVE', 'MESSAGE_REACTION_REMOVE_ALL', 'CHANNEL_PINS_UPDATE', 'MESSAGE_DELETE_BULK', 'WEBHOOKS_UPDATE'],
      owner: config.owner || '0',
      commandPrefix: config.commandPrefix || '!',
      unknownCommandResponse: false,
      disableMentions: 'everyone',
      messageCacheMaxSize: 0,
      retryLimit: 0,
      ws: {
        intents: ["GUILD_MEMBERS", "GUILDS", "GUILD_MESSAGES"],
      },
    })

    this.bot.setProvider(new SettingProvider())

    // Instantiate the shard's Cache singleton to interface with the main process.
    // A global variable is used here because the cache is dependent on the client
    // being initialized, but I don't like the idea of having to pass down the cache
    // from this object into every instance (DiscordMember, DiscordServer). This seemed
    // like the best solution.
    global.Cache = new Cache(this.bot)
    this.shardClientUtil = global.Cache.shardClientUtil

    // Set a reference to this instance inside of the client
    // for use in Commando modules. Is this bad? Probably.
    this.bot.discordBot = this

    // Events

    // We use .bind(this) so that the context remains within
    // the class and not the event.
    // this.bot.on('debug', (info) => { console.log(`[DEBUG SHARD${this.bot.shard.ids[0]}] ${info}`)})
    this.bot.on('warn', (info) => { console.log(`[WARN SHARD${this.bot.shard.ids[0]}] ${info}`)})
    this.bot.on('rateLimit', (err) => { console.error(`[RL SHARD${this.bot.shard.ids[0]}] ${JSON.stringify(err)}`)})
    this.bot.on('error', (err) => { console.error(`[ERR SHARD${this.bot.shard.ids[0]}] `, err)})
    this.bot.on('shardError', (err, id) => { console.error(`[WS SHARD${id}] ${JSON.stringify(err)}`)})
    this.bot.on('shardDisconnect', (event, id) => { console.error(`[WS SHARD${id}] ${JSON.stringify(event)}`)})
    process.on('unhandledRejection', (reason, promise) => {
      console.log('Unhandled Rejection at:', promise, 'reason:', reason);
    });
    this.bot.on('ready', this.ready.bind(this))
    this.bot.on('guildMemberAdd', this.guildMemberAdd.bind(this))

    this.bot.on('message', this.message.bind(this))

    this.bot.on('invalidated', () => { // This should never happen!
      console.error(`Sesson on shard ${this.bot.shard.ids[0]} invalidated - exiting!`)
      process.exit(0)
    })

    if (config.loud) {
      this.bot.on('error', (message) => console.log(message))
      process.on('unhandledRejection', (reason, promise) => {
        console.log('Unhandled rejection at:', promise, 'reason:', reason)
      })
    }

    this.bot.dispatcher.addInhibitor(msg => {
      if (!msg.guild) {
        return
      }

      if (this.blacklist[msg.guild.ownerID]) {
        msg.reply("This server is blacklisted!")
        return 'blacklisted'
      }
    })

    if (this.isPremium()) {
      this.bot.dispatcher.addInhibitor(msg => {
        if (msg.guild && !this.authorizedOwners.includes(msg.guild.ownerID)) {
          if (this.authorizedOwners.length === 0) {
            msg.reply('Sorry, the authorized users list is still being downloaded. This occurs when the bot has recently restarted. Please wait a few seconds and try again.')
          } else {
            msg.reply(`Sorry, this server isn't authorized to use RoVer Plus.${msg.member.hasPermission(['MANAGE_GUILD']) ? ' The server owner needs to donate at <https://www.patreon.com/erynlynn>, or you can invite the regular RoVer bot at <https://RoVer.link>.' : ''} If you are a patron encountering this issue, try running '!transferplus ${msg.guild.ownerID}'`) // notify sender to donate only if they're an "admin"
          }

          return 'not_premium'
        }
      })

      this.updatePatrons()

      setInterval(() => {
        const beforePatrons = this.authorizedOwners
        this.updatePatrons().catch((updateError) => {
          console.error(`Patron update failed! ${updateError}`)

          this.authorizedOwners = beforePatrons
        })
      }, 5 * 60 * 1000)
    }

    // Register commands
    this.bot.registry
      .registerGroup('rover', 'RoVer')
      .registerDefaultTypes()
      .registerDefaultGroups()
      .registerDefaultCommands({
        ping: false,
        commandState: false,
        prefix: true,
        help: true,
        unknownCommand: false
      })
      .registerCommandsIn(path.join(__dirname, 'commands'))

    // Login.
    this.bot.login(process.env.CLIENT_TOKEN)

    this.updateBlacklist().catch(console.error)
  }

  isPremium () {
    return !!config.patreonAccessToken
  }

  async updateBlacklist () {
    if (!config.banServer) {
      return false
    }

    const response = await request(`https://discord.com/api/v6/guilds/${config.banServer}/bans`, {
      json: true,
      headers: {
        Authorization: `Bot ${config.token}`
      }
    })

    response.forEach(ban => {
      this.blacklist[ban.user.id] = true
    })
  }

  async updateBlacklist () {
    if (!config.banServer) {
      return false
    }

    const response = await request(`https://discord.com/api/v6/guilds/${config.banServer}/bans`, {
      json: true,
      headers: {
        Authorization: `Bot ${config.token}`
      }
    })

    response.forEach(ban => {
      this.blacklist[ban.user.id] = true
    })
  }

  async updatePatrons (page, newAuthorizedOwners) {
    if (!page) {
      newAuthorizedOwners = []
    }

    const transferFilePath = path.join(__dirname, './data/transfers.csv')

    if (await fs.exists(transferFilePath)) {
      const contents = await fs.readFile(transferFilePath, {
        encoding: 'utf8'
      })

      this.patronTransfers = contents.split(/\n\r?/)
        .map(line => line.split(','))
        .reduce((a, transfer) => {
          a[transfer[0]] = transfer[1]
          return a
        }, {})
    }

    const url = page || `https://www.patreon.com/api/oauth2/api/campaigns/${config.patreonCampaignId}/pledges?include=patron.null`

    const response = await request(url, {
      json: true,
      headers: {
        Authorization: `Bearer ${config.patreonAccessToken}`
      }
    })

    newAuthorizedOwners = [
      config.owner || '0',
      ...(config.patreonOverrideOwners || []),
      ...newAuthorizedOwners,
      ...(
        response.data.filter(pledge => (
          pledge.attributes.declined_since === null
        )).map(pledge => (
          response.included.find(include => include.id === pledge.relationships.patron.data.id)
        )).filter(include => (
          include.attributes.social_connections &&
          include.attributes.social_connections.discord
        )).map(include => (
          include.attributes.social_connections.discord.user_id
        ))
      )
    ].map(id => this.patronTransfers[id] || id)

    if (response.links && response.links.next) {
      return this.updatePatrons(response.links.next, newAuthorizedOwners)
    } else {
      this.authorizedOwners = newAuthorizedOwners
    }
  }

  /**
   * Called when the bot is ready and has logged in.
   * @listens Discord.Client#ready
   * @memberof DiscordBot
   */
  ready () {
    console.log(`Shard ${this.bot.shard.ids[0]} is ready, serving ${this.bot.guilds.cache.array().length} guilds.`)

    // Set status message to the default until we get info from master process
    this.bot.user.setActivity('rover.link', { type: "LISTENING" })
  }

  /**
   * This method is called when a user sends a message, but it's used
   * for setting their nickname back to what it should be if they've
   * changed it. Only active if lockNicknames is true in config.
   * @listens Discord.Client#message
   * @param {Message} message The new message.
   * @memberof DiscordBot
   */
  async message (message) {
    // Don't want to do anything if this is a DM or message was sent by the bot itself.
    // Additionally, if the message is !verify, we don't want to run it twice (since it
    // will get picked up by the command anyway)
    if (!message.guild || message.author.id === this.bot.user.id || message.cleanContent.toLowerCase() === message.guild.commandPrefix + 'verify' || message.author.bot) {
      return
    }

    // We call discordMember.verify but we want to retain the cache
    // and we don't want it to post any announcements.
    const server = await this.getServer(message.guild.id)
    const member = await server.getMember(message.author.id)
    if (!member) return

    // If this is the verify channel, we want to delete the message and just verify the user if they aren't an admin.
    if (server.getSetting('verifyChannel') === message.channel.id && message.cleanContent.toLowerCase() !== message.guild.commandPrefix + 'verify' && !(this.bot.isOwner(message.author) || message.member.hasPermission('MANAGE_GUILD') || message.member.roles.cache.find(role => role.name === 'RoVer Admin'))) {
      if (message.channel.permissionsFor(message.guild.me).has('MANAGE_MESSAGES')) {
        message.delete().catch(console.error)
      }
      return member.verify({ message })
    }

    if (!config.disableAutoUpdate && member.shouldUpdateNickname(message.member.displayName) && config.lockNicknames) {
      // As a last resort, we just verify with cache on every message sent.
      await member.verify({
        announce: false,
        clearBindingsCache: false
      })
    }
  }

  /**
   * This is called when a user joins any Discord server.
   * @listens Discord.Client#guildMemberAdd
   * @param {GuildMember} member The new guild member
   * @memberof DiscordBot
   */
  async guildMemberAdd (member) {
    if (member.user.bot) return

    const server = await this.getServer(member.guild.id)

    if (server.getSetting('joinDM') === false) {
      return
    }

    const discordMember = await server.getMember(member.id)
    if (!member) return

    // Check the guild's verification level
    const securityLevel = member.guild.verificationLevel
    const securityMessageIntro = `Welcome to ${member.guild.name}! This Discord server uses a Roblox account verification system to keep our community safe. Due to this server's security settings,`
    if (securityLevel === 'MEDIUM' && (member.joinedTimestamp - member.user.createdTimestamp < 300000)) {
      member.send(`${securityMessageIntro} you must wait until your account is at least 5 minutes old to verify. Once the time is up, run \`${member.guild.commandPrefix}verify\` in the server to verify.`).catch(() => {})
      return
    } else if (securityLevel === 'HIGH') {
      member.send(`${securityMessageIntro} you must wait 10 minutes to verify if you do not have a phone number linked to your Discord account. If you do have a linked phone number, you may immediately run \`${member.guild.commandPrefix}verify\` in the server.`).catch(() => {})
      return
    } else if (securityLevel === 'VERY_HIGH') {
      member.send(`${securityMessageIntro} you must link your phone number to your Discord account. If you have already done so, you may run \`${member.guild.commandPrefix}verify\` in the server.`).catch(() => {})
      return
    }
    const action = await discordMember.verify()

    try {
      if (action.status) {
        member.send(server.getWelcomeMessage(action, member)).catch(() => {})
      } else if (!action.status && action.nonFatal) {
        member.send(`Welcome to ${member.guild.name}! You are already verified, but something went wrong when updating your roles. Try running \`${member.guild.commandPrefix}verify\` in the server for more information.`).catch(() => {})
      } else {
        member.send(`Welcome to ${member.guild.name}! This Discord server uses a Roblox account verification system to keep our community safe. Verifying your account is quick and safe and doesn't require any information other than your username. All you have to do is either join a game or put a code in your profile, and you're in!\n\nVisit the following link to verify your Roblox account: ${Util.getVerifyLink(member.guild)}`).catch(() => {})
      }
    } catch (e) {}
  }

  /**
   * Sets the bot's status text.
   * @param {string} text The status message.
   * @param {string} activityType The activity type.
   * @memberof DiscordBot
   */
  setActivity (text, activityType) {
    if (!this.bot || !this.bot.user) return

    this.bot.user.setActivity(text || 'http://eryn.io/RoVer', { type: activityType })
  }

  /**
   * This is used to get the DiscordServer instance associated
   * with the specific guild id.
   * @param {Snowflake} id Guild id
   * @returns {Promise<DiscordServer>} DiscordServer
   * @memberof DiscordBot
   */
  async getServer (id) {
    if (!this.servers[id]) {
      this.servers[id] = new DiscordServer(this, id)
      await this.servers[id].loadSettings()
    } else if (!this.servers[id].areSettingsLoaded) {
      await this.servers[id].loadSettings()
    }
    return this.servers[id]
  }

  /**
   * This is called by the update server when a user verifies
   * online. It updates the member in every DiscordServer they
   * are in.
   * @param {object} args An object with keys `id` (string) and `guilds` (array)
   * @memberof DiscordBot
   */
  async globallyUpdateMember (args) {
    const { id, guilds } = args

    // Start off by clearing their global cache.
    await DiscordServer.clearMemberCache(id)

    let firstRun = true

    // Iterate through all of the guilds the bot is in.
    for (const guildId of guilds) {
      try {
        if (!this.bot.guilds.cache.has(guildId)) continue

        const guild = this.bot.guilds.resolve(guildId)
        const server = await this.getServer(guild.id)

        const member = await server.getMember(id)
        if (!member) continue
        const action = await member.verify({
          // We want to clear the group rank bindings cache because
          // this is the first iteration.
          clearBindingsCache: firstRun
        })

        if (!action.status && !action.nonFatal) {
          // If there's a fatal error, don't continue with the rest.
          break
        } else if (action.status && server.hasCustomWelcomeMessage()) {
          // It worked, checking if there's a custom welcome message.
          await this.bot.users.fetch(id)

          const guildMember = await this.bot.guilds.resolve(guild.id).members.fetch(id)
          guildMember.send(server.getWelcomeMessage(action, guildMember)).catch(() => {})
        }

        firstRun = false
      } catch (e) {
        continue
      }
    }
  }
}

module.exports = DiscordBot
<commit_msg>Fix Prefix for patron message<commit_after>const path = require('path')
const Discord = require('discord.js-commando')
const request = require('request-promise')
const config = require('./data/client.json')
const DiscordServer = require('./DiscordServer')
const { Cache } = require('./GlobalCache')
const requestDebug = require('request-debug')
const SettingProvider = require('./commands/SettingProvider')
const Util = require('./Util')
const fs = require('mz/fs')

if (config.loud) requestDebug(request, (type, data) => console.log(`${type} ${data.debugId} : ${data.uri || data.statusCode}`))

/**
 * The main Discord bot class, only one per shard.
 * @class DiscordBot
 */
class DiscordBot {
  constructor () {
    this.initialize()
    this.servers = {}
    this.authorizedOwners = []
    this.patronTransfers = {}
    this.blacklist = {}
  }

  /**
   * Initialize the bot, hook up events, and log in.
   * @memberof DiscordBot
   */
  initialize () {
    this.bot = new Discord.Client({
      apiRequestMethod: config.apiRequestMethod || 'sequential',
      disabledEvents: ['TYPING_START', 'VOICE_STATE_UPDATE', 'PRESENCE_UPDATE', 'MESSAGE_DELETE', 'MESSAGE_UPDATE', 'CHANNEL_PINS_UPDATE', 'MESSAGE_REACTION_ADD', 'MESSAGE_REACTION_REMOVE', 'MESSAGE_REACTION_REMOVE_ALL', 'CHANNEL_PINS_UPDATE', 'MESSAGE_DELETE_BULK', 'WEBHOOKS_UPDATE'],
      owner: config.owner || '0',
      commandPrefix: config.commandPrefix || '!',
      unknownCommandResponse: false,
      disableMentions: 'everyone',
      messageCacheMaxSize: 0,
      retryLimit: 0,
      ws: {
        intents: ["GUILD_MEMBERS", "GUILDS", "GUILD_MESSAGES"],
      },
    })

    this.bot.setProvider(new SettingProvider())

    // Instantiate the shard's Cache singleton to interface with the main process.
    // A global variable is used here because the cache is dependent on the client
    // being initialized, but I don't like the idea of having to pass down the cache
    // from this object into every instance (DiscordMember, DiscordServer). This seemed
    // like the best solution.
    global.Cache = new Cache(this.bot)
    this.shardClientUtil = global.Cache.shardClientUtil

    // Set a reference to this instance inside of the client
    // for use in Commando modules. Is this bad? Probably.
    this.bot.discordBot = this

    // Events

    // We use .bind(this) so that the context remains within
    // the class and not the event.
    // this.bot.on('debug', (info) => { console.log(`[DEBUG SHARD${this.bot.shard.ids[0]}] ${info}`)})
    this.bot.on('warn', (info) => { console.log(`[WARN SHARD${this.bot.shard.ids[0]}] ${info}`)})
    this.bot.on('rateLimit', (err) => { console.error(`[RL SHARD${this.bot.shard.ids[0]}] ${JSON.stringify(err)}`)})
    this.bot.on('error', (err) => { console.error(`[ERR SHARD${this.bot.shard.ids[0]}] `, err)})
    this.bot.on('shardError', (err, id) => { console.error(`[WS SHARD${id}] ${JSON.stringify(err)}`)})
    this.bot.on('shardDisconnect', (event, id) => { console.error(`[WS SHARD${id}] ${JSON.stringify(event)}`)})
    process.on('unhandledRejection', (reason, promise) => {
      console.log('Unhandled Rejection at:', promise, 'reason:', reason);
    });
    this.bot.on('ready', this.ready.bind(this))
    this.bot.on('guildMemberAdd', this.guildMemberAdd.bind(this))

    this.bot.on('message', this.message.bind(this))

    this.bot.on('invalidated', () => { // This should never happen!
      console.error(`Sesson on shard ${this.bot.shard.ids[0]} invalidated - exiting!`)
      process.exit(0)
    })

    if (config.loud) {
      this.bot.on('error', (message) => console.log(message))
      process.on('unhandledRejection', (reason, promise) => {
        console.log('Unhandled rejection at:', promise, 'reason:', reason)
      })
    }

    this.bot.dispatcher.addInhibitor(msg => {
      if (!msg.guild) {
        return
      }

      if (this.blacklist[msg.guild.ownerID]) {
        msg.reply("This server is blacklisted!")
        return 'blacklisted'
      }
    })

    if (this.isPremium()) {
      this.bot.dispatcher.addInhibitor(msg => {
        if (msg.guild && !this.authorizedOwners.includes(msg.guild.ownerID)) {
          if (this.authorizedOwners.length === 0) {
            msg.reply('Sorry, the authorized users list is still being downloaded. This occurs when the bot has recently restarted. Please wait a few seconds and try again.')
          } else {
            msg.reply(`Sorry, this server isn't authorized to use RoVer Plus.${msg.member.hasPermission(['MANAGE_GUILD']) ? ' The server owner needs to donate at <https://www.patreon.com/erynlynn>, or you can invite the regular RoVer bot at <https://RoVer.link>.' : ''} If you are a patron encountering this issue, try running '${this.bot.commandPrefix}transferplus ${msg.guild.ownerID}'`) // notify sender to donate only if they're an "admin"
          }

          return 'not_premium'
        }
      })

      this.updatePatrons()

      setInterval(() => {
        const beforePatrons = this.authorizedOwners
        this.updatePatrons().catch((updateError) => {
          console.error(`Patron update failed! ${updateError}`)

          this.authorizedOwners = beforePatrons
        })
      }, 5 * 60 * 1000)
    }

    // Register commands
    this.bot.registry
      .registerGroup('rover', 'RoVer')
      .registerDefaultTypes()
      .registerDefaultGroups()
      .registerDefaultCommands({
        ping: false,
        commandState: false,
        prefix: true,
        help: true,
        unknownCommand: false
      })
      .registerCommandsIn(path.join(__dirname, 'commands'))

    // Login.
    this.bot.login(process.env.CLIENT_TOKEN)

    this.updateBlacklist().catch(console.error)
  }

  isPremium () {
    return !!config.patreonAccessToken
  }

  async updateBlacklist () {
    if (!config.banServer) {
      return false
    }

    const response = await request(`https://discord.com/api/v6/guilds/${config.banServer}/bans`, {
      json: true,
      headers: {
        Authorization: `Bot ${config.token}`
      }
    })

    response.forEach(ban => {
      this.blacklist[ban.user.id] = true
    })
  }

  async updateBlacklist () {
    if (!config.banServer) {
      return false
    }

    const response = await request(`https://discord.com/api/v6/guilds/${config.banServer}/bans`, {
      json: true,
      headers: {
        Authorization: `Bot ${config.token}`
      }
    })

    response.forEach(ban => {
      this.blacklist[ban.user.id] = true
    })
  }

  async updatePatrons (page, newAuthorizedOwners) {
    if (!page) {
      newAuthorizedOwners = []
    }

    const transferFilePath = path.join(__dirname, './data/transfers.csv')

    if (await fs.exists(transferFilePath)) {
      const contents = await fs.readFile(transferFilePath, {
        encoding: 'utf8'
      })

      this.patronTransfers = contents.split(/\n\r?/)
        .map(line => line.split(','))
        .reduce((a, transfer) => {
          a[transfer[0]] = transfer[1]
          return a
        }, {})
    }

    const url = page || `https://www.patreon.com/api/oauth2/api/campaigns/${config.patreonCampaignId}/pledges?include=patron.null`

    const response = await request(url, {
      json: true,
      headers: {
        Authorization: `Bearer ${config.patreonAccessToken}`
      }
    })

    newAuthorizedOwners = [
      config.owner || '0',
      ...(config.patreonOverrideOwners || []),
      ...newAuthorizedOwners,
      ...(
        response.data.filter(pledge => (
          pledge.attributes.declined_since === null
        )).map(pledge => (
          response.included.find(include => include.id === pledge.relationships.patron.data.id)
        )).filter(include => (
          include.attributes.social_connections &&
          include.attributes.social_connections.discord
        )).map(include => (
          include.attributes.social_connections.discord.user_id
        ))
      )
    ].map(id => this.patronTransfers[id] || id)

    if (response.links && response.links.next) {
      return this.updatePatrons(response.links.next, newAuthorizedOwners)
    } else {
      this.authorizedOwners = newAuthorizedOwners
    }
  }

  /**
   * Called when the bot is ready and has logged in.
   * @listens Discord.Client#ready
   * @memberof DiscordBot
   */
  ready () {
    console.log(`Shard ${this.bot.shard.ids[0]} is ready, serving ${this.bot.guilds.cache.array().length} guilds.`)

    // Set status message to the default until we get info from master process
    this.bot.user.setActivity('rover.link', { type: "LISTENING" })
  }

  /**
   * This method is called when a user sends a message, but it's used
   * for setting their nickname back to what it should be if they've
   * changed it. Only active if lockNicknames is true in config.
   * @listens Discord.Client#message
   * @param {Message} message The new message.
   * @memberof DiscordBot
   */
  async message (message) {
    // Don't want to do anything if this is a DM or message was sent by the bot itself.
    // Additionally, if the message is !verify, we don't want to run it twice (since it
    // will get picked up by the command anyway)
    if (!message.guild || message.author.id === this.bot.user.id || message.cleanContent.toLowerCase() === message.guild.commandPrefix + 'verify' || message.author.bot) {
      return
    }

    // We call discordMember.verify but we want to retain the cache
    // and we don't want it to post any announcements.
    const server = await this.getServer(message.guild.id)
    const member = await server.getMember(message.author.id)
    if (!member) return

    // If this is the verify channel, we want to delete the message and just verify the user if they aren't an admin.
    if (server.getSetting('verifyChannel') === message.channel.id && message.cleanContent.toLowerCase() !== message.guild.commandPrefix + 'verify' && !(this.bot.isOwner(message.author) || message.member.hasPermission('MANAGE_GUILD') || message.member.roles.cache.find(role => role.name === 'RoVer Admin'))) {
      if (message.channel.permissionsFor(message.guild.me).has('MANAGE_MESSAGES')) {
        message.delete().catch(console.error)
      }
      return member.verify({ message })
    }

    if (!config.disableAutoUpdate && member.shouldUpdateNickname(message.member.displayName) && config.lockNicknames) {
      // As a last resort, we just verify with cache on every message sent.
      await member.verify({
        announce: false,
        clearBindingsCache: false
      })
    }
  }

  /**
   * This is called when a user joins any Discord server.
   * @listens Discord.Client#guildMemberAdd
   * @param {GuildMember} member The new guild member
   * @memberof DiscordBot
   */
  async guildMemberAdd (member) {
    if (member.user.bot) return

    const server = await this.getServer(member.guild.id)

    if (server.getSetting('joinDM') === false) {
      return
    }

    const discordMember = await server.getMember(member.id)
    if (!member) return

    // Check the guild's verification level
    const securityLevel = member.guild.verificationLevel
    const securityMessageIntro = `Welcome to ${member.guild.name}! This Discord server uses a Roblox account verification system to keep our community safe. Due to this server's security settings,`
    if (securityLevel === 'MEDIUM' && (member.joinedTimestamp - member.user.createdTimestamp < 300000)) {
      member.send(`${securityMessageIntro} you must wait until your account is at least 5 minutes old to verify. Once the time is up, run \`${member.guild.commandPrefix}verify\` in the server to verify.`).catch(() => {})
      return
    } else if (securityLevel === 'HIGH') {
      member.send(`${securityMessageIntro} you must wait 10 minutes to verify if you do not have a phone number linked to your Discord account. If you do have a linked phone number, you may immediately run \`${member.guild.commandPrefix}verify\` in the server.`).catch(() => {})
      return
    } else if (securityLevel === 'VERY_HIGH') {
      member.send(`${securityMessageIntro} you must link your phone number to your Discord account. If you have already done so, you may run \`${member.guild.commandPrefix}verify\` in the server.`).catch(() => {})
      return
    }
    const action = await discordMember.verify()

    try {
      if (action.status) {
        member.send(server.getWelcomeMessage(action, member)).catch(() => {})
      } else if (!action.status && action.nonFatal) {
        member.send(`Welcome to ${member.guild.name}! You are already verified, but something went wrong when updating your roles. Try running \`${member.guild.commandPrefix}verify\` in the server for more information.`).catch(() => {})
      } else {
        member.send(`Welcome to ${member.guild.name}! This Discord server uses a Roblox account verification system to keep our community safe. Verifying your account is quick and safe and doesn't require any information other than your username. All you have to do is either join a game or put a code in your profile, and you're in!\n\nVisit the following link to verify your Roblox account: ${Util.getVerifyLink(member.guild)}`).catch(() => {})
      }
    } catch (e) {}
  }

  /**
   * Sets the bot's status text.
   * @param {string} text The status message.
   * @param {string} activityType The activity type.
   * @memberof DiscordBot
   */
  setActivity (text, activityType) {
    if (!this.bot || !this.bot.user) return

    this.bot.user.setActivity(text || 'http://eryn.io/RoVer', { type: activityType })
  }

  /**
   * This is used to get the DiscordServer instance associated
   * with the specific guild id.
   * @param {Snowflake} id Guild id
   * @returns {Promise<DiscordServer>} DiscordServer
   * @memberof DiscordBot
   */
  async getServer (id) {
    if (!this.servers[id]) {
      this.servers[id] = new DiscordServer(this, id)
      await this.servers[id].loadSettings()
    } else if (!this.servers[id].areSettingsLoaded) {
      await this.servers[id].loadSettings()
    }
    return this.servers[id]
  }

  /**
   * This is called by the update server when a user verifies
   * online. It updates the member in every DiscordServer they
   * are in.
   * @param {object} args An object with keys `id` (string) and `guilds` (array)
   * @memberof DiscordBot
   */
  async globallyUpdateMember (args) {
    const { id, guilds } = args

    // Start off by clearing their global cache.
    await DiscordServer.clearMemberCache(id)

    let firstRun = true

    // Iterate through all of the guilds the bot is in.
    for (const guildId of guilds) {
      try {
        if (!this.bot.guilds.cache.has(guildId)) continue

        const guild = this.bot.guilds.resolve(guildId)
        const server = await this.getServer(guild.id)

        const member = await server.getMember(id)
        if (!member) continue
        const action = await member.verify({
          // We want to clear the group rank bindings cache because
          // this is the first iteration.
          clearBindingsCache: firstRun
        })

        if (!action.status && !action.nonFatal) {
          // If there's a fatal error, don't continue with the rest.
          break
        } else if (action.status && server.hasCustomWelcomeMessage()) {
          // It worked, checking if there's a custom welcome message.
          await this.bot.users.fetch(id)

          const guildMember = await this.bot.guilds.resolve(guild.id).members.fetch(id)
          guildMember.send(server.getWelcomeMessage(action, guildMember)).catch(() => {})
        }

        firstRun = false
      } catch (e) {
        continue
      }
    }
  }
}

module.exports = DiscordBot
<|endoftext|>


<commit_before>/**
 * Functions to create and operate on CPUs.
 *
 * @module Engine/CPU
 */

/**
 * A CPU in a virgin state.
 *
 * @typedef  {Object}  CPU
 * @property {String}  phase - Current phase of execution
 * @property {String}  next  - Next phase to be executed
 * @property {Boolean} rst   - Reset flag
 * @property {Boolean} read  - Read request flag
 * @property {Boolean} write - Write request flag
 * @property {Number}  a     - Accumulator
 * @property {Number}  dr    - Data register
 * @property {Number}  ir    - Instruction register
 * @property {Number}  ar    - Address register
 * @property {Number}  pc    - Program counter
 */

/**
 * Create a CPU in a virgin state.
 *
 * @method create
 * @returns {CPU}
 */
export function create() {
	return {
		phase: 'RESET',
		next: 'FETCH',
		rst: false,
		read: false,
		write: false,
		a: 0,
		dr: 0,
		ir: 0,
		ar: 0,
		pc: 0,
	};
}

/**
 * Executes one CPU cycle, returning the new state.
 *
 * @param   {CPU} state
 * @returns {CPU}
 */
export function cycle(state) {
	var receiver;
	const phase = state.next;
	switch (true) {
		case state.rst:       receiver = create; break;
		case phase in phases: receiver = phases[phase]; break;
		default:              return { ...state };
	}
	return { ...state, phase, ...receiver(state) };
}

/**
 * Handlers for every phase the CPU can transition to.
 */
export const phases = {
	FETCH, FETCH2,
	OPR, CLR, NOT, INC, ROL, ROR,
	LD, LD2, ST,
	ADD, ADD2,
	AND, AND2,
	JMP, JZ,
	OUT,
};

/**
 * A map between the opcodes supported by this CPU and the phase
 * transitions that handle their execution.
 *
 * | Opcode | Mnemonic | Operand | T | Function                            |
 * |--------|----------|---------|---|-------------------------------------|
 * | 000    | OPR      | op      | 1 | Operate on accumulator              |
 * | 001    | LD       | addr    | 2 | Load memory into accumulator        |
 * | 010    | ST       | addr    | 1 | Store accumulator into memory       |
 * | 011    | ADD      | addr    | 2 | Add memory location to accumulator  |
 * | 100    | AND      | addr    | 2 | Bitwise AND memory with accumulator |
 * | 101    | JMP      | addr    | 1 | Inconditional jump                  |
 * | 110    | JZ       | addr    | 1 | Jump if accumulator is zero         |
 * | 111    | OUT      |         | 1 | Output accumulator                  |
 */
export const opcodes = {
	0b000: 'OPR',
	0b001: 'LD',
	0b010: 'ST',
	0b011: 'ADD',
	0b100: 'AND',
	0b101: 'JMP',
	0b110: 'JZ',
	0b111: 'OUT',
};

/**
 * A map between the operators supported by the OPR instruction and the
 * phase transitions that handle their execution. All operators whose
 * flag is expressed will run in the same cycle and in the same order as
 * they are presented here.
 *
 * | Operator | Mnemonic | Function                     |
 * |----------|----------|------------------------------|
 * | 00001    | CLR      | Clear accumulator            |
 * | 00010    | NOT      | One's complement accumulator |
 * | 00100    | INC      | Increment accumulator        |
 * | 01000    | ROL      | Rotate accumulator left      |
 * | 10000    | ROR      | Rotate accumulator right     |
 */
export const operators = {
	0b00001: 'CLR',
	0b00010: 'NOT',
	0b00100: 'INC',
	0b01000: 'ROL',
	0b10000: 'ROR',
};

/**
 * Decodes a word into into opcode, operand and phase transition.
 *
 * Produces values for the instruction and address register, and the cpu
 * phase to transition to in order to execute the opcode.
 *
 * @method  decode
 * @param   {Number} word
 * @returns {Object}
 */
export function decode(word) {
	const ir = (word & 0b11100000) >> 5;
	const ar = (word & 0b00011111) >> 0;
	const opcode = opcodes[ir];
	return { ir, ar, opcode };
}

/**
 * Fetch the instruction at PC.
 *
 * Points the address register to the program counter, raises the read
 * request flag, and waits for memory to reply.
 *
 * @param   {CPU} state
 * @returns {CPU}
 */
export function FETCH(state) {
	return {
		ar: state.pc,
		read: true,
		write: false,
		next: 'FETCH2',
	};
}

/**
 * Second step of the instruction fetch phase.
 *
 * Reads the value arriving from memory on the data register and writes
 * it into the instruction and address registers, then increments the
 * program counter. Will transition to the handler for the opcode
 * expressed by the instruction register.
 *
 * @param   {CPU} state
 * @returns {CPU}
 */
export function FETCH2(state) {
	const {opcode, ir, ar} = decode(state.dr);
	return {
		ir, ar,
		read: false,
		pc: state.pc + 1,
		next: opcode,
	};
}

/**
 * Load memory into accumulator.
 *
 * Points the address register to the memory location to be read, raises
 * the read request flag, and waits for memory to reply.
 *
 * @param   {CPU} state
 * @returns {CPU}
 */
function LD(state) {
	return {
		ar: state.ar,
		read: true,
		next: 'LD2',
	};
}

/**
 * Second step of the load operation.
 *
 * Reads the value arriving from memory on the data register and writes
 * it into the accumulator.
 *
 * @param   {CPU} state
 * @returns {CPU}
 */
function LD2(state) {
	return {
		a: state.dr,
		read: false,
		next: 'FETCH',
	};
}

/**
 * Store accumulator into memory.
 *
 * Points the address register to the memory location to be written,
 * writes the contents of the accumulator on the data register, and
 * triggers the write request flag.
 *
 * @param   {CPU} state
 * @returns {CPU}
 */
function ST(state) {
	return {
		ar: state.ar,
		dr: state.a,
		write: true,
		next: 'FETCH',
	};
}

/**
 * Add memory location to accumulator.
 *
 * Points the address register to the memory location of the operand,
 * raises the read request flag, and waits for memory to reply.
 *
 * @param   {CPU} state
 * @returns {CPU}
 */
function ADD(state) {
	return {
		ar: state.ar,
		read: true,
		next: 'ADD2',
	};
}

/**
 * Second step of the addition operation.
 *
 * Reads the value arriving from memory on the data register, adds it to
 * the accumulator, and stores the results into the accumulator.
 *
 * @param   {CPU} state
 * @returns {CPU}
 */
function ADD2(state) {
	return {
		a: (state.a + state.dr) % 256,
		read: false,
		next: 'FETCH',
	};
}

/**
 * Bitwise AND memory with accumulator.
 *
 * Points the address register to the memory location of the operand,
 * raises the read request flag, and waits for memory to reply.
 *
 * @param   {CPU} state
 * @returns {CPU}
 */
function AND(state) {
	return {
		read: true,
		next: 'AND2',
	};
}

/**
 * Second step of the bitwise AND operation.
 *
 * Reads the value arriving from memory on the data register, ANDs it
 * with the accumulator, and stores the results into the accumulator.
 *
 * @param   {CPU} state
 * @returns {CPU}
 */
function AND2(state) {
	return {
		a: (state.a & state.dr) % 256,
		read: false,
		next: 'FETCH',
	};
}

/**
 * Inconditional jump.
 *
 * Points the program counter to the address in the address register.
 *
 * @param   {CPU} state
 * @returns {CPU}
 */
function JMP(state) {
	return {
		pc: state.ar & 0b00011111,
		next: 'FETCH',
	};
}

/**
 * Jump if accumulator is zero.
 *
 * Points the program counter to the address in the address register if
 * the accumulator is zero.
 *
 * @param   {CPU} state
 * @returns {CPU}
 */
function JZ(state) {
	const z = state.a === 0;
	const ar = state.ar & 0b00011111;
	const pc = z? ar : state.pc;
	return {
		pc,
		next: 'FETCH',
	};
}

/**
 * Output accumulator.
 *
 * Outputs the value of the accumulator to the standard output.
 *
 * @param   {CPU} state
 * @returns {CPU}
 */
function OUT(state) {
	/* eslint-disable */
	// TODO: Implement output device or generic I/O.
	console.log(state.a);
	/* eslint-enable */
	return {
		next: 'FETCH',
	};
}

/**
 * Operate on accumulator.
 *
 * Executes the operation expressed by the address register on the
 * accumulator, and stores the results into the accumulator. Requires no
 * operands.
 *
 * @param   {CPU} state
 * @returns {CPU}
 */
function OPR(state) {
	const newState = Object
		.keys(operators)
		.map(op => parseInt(op, 10)).sort()
		.filter(op => state.ar & op)
		.reduce(function(diff, op) {
			var handler = phases[operators[op]];
			return { ...diff, ...handler({ ...state, ...diff }) };
		}, {});
	newState.next = 'FETCH';
	return newState;
}

/**
 * Clear accumulator.
 *
 * Resets the accumulator to zero.
 *
 * @param   {CPU} state
 * @returns {CPU}
 */
function CLR(state) {
	return {
		a: 0,
	};
}

/**
 * One's complement accumulator.
 *
 * Replaces the value in the accumulator with its one's complement.
 *
 * @param   {CPU} state
 * @returns {CPU}
 */
function NOT(state) {
	return {
		a: ~state.a & 0b11111111,
	};
}

/**
 * Increment accumulator.
 *
 * Increments the value in the accumulator by 1.
 *
 * @param   {CPU} state
 * @returns {CPU}
 */
function INC(state) {
	return {
		a: (state.a + 1) % 256,
	};
}

/**
 * Rotate accumulator left.
 *
 * Rotates the accumulator to the left by 1.
 *
 * @param   {CPU} state
 * @returns {CPU}
 */
function ROL(state) {
	return {
		a: (state.a << 1) % 256,
	};
}

/**
 * Rotate accumulator right.
 *
 * Rotates the accumulator to the right by 1.
 *
 * @param   {CPU} state
 * @returns {CPU}
 */
function ROR(state) {
	return {
		a: (state.a >> 1) % 256,
	};
}

/**
 * Returns an overview of the state of a CPU as text.
 *
 * @param   {CPU} state
 * @returns {String}
 */
export function toString(state) {
	const rst = state.rst? 'on' : 'off';
	const read = state.read? 'on' : 'off';
	const write = state.write? 'on' : 'off';
	const {phase, next, a, dr, ir, ar, pc} = state;
	return `CPU ${phase}->${next} RST:${rst} READ:${read} WRITE:${write} `
	     + `A:${a} AR:${ar} DR:${dr} IR:${ir} PC:${pc}`;
}
<commit_msg>Opcodes are now described by additional properties.<commit_after>/**
 * Functions to create and operate on CPUs.
 *
 * @module Engine/CPU
 */

/**
 * A CPU in a virgin state.
 *
 * @typedef  {Object}  CPU
 * @property {String}  phase - Current phase of execution
 * @property {String}  next  - Next phase to be executed
 * @property {Boolean} rst   - Reset flag
 * @property {Boolean} read  - Read request flag
 * @property {Boolean} write - Write request flag
 * @property {Number}  a     - Accumulator
 * @property {Number}  dr    - Data register
 * @property {Number}  ir    - Instruction register
 * @property {Number}  ar    - Address register
 * @property {Number}  pc    - Program counter
 */

/**
 * Create a CPU in a virgin state.
 *
 * @method create
 * @returns {CPU}
 */
export function create() {
	return {
		phase: 'RESET',
		next: 'FETCH',
		rst: false,
		read: false,
		write: false,
		a: 0,
		dr: 0,
		ir: 0,
		ar: 0,
		pc: 0,
	};
}

/**
 * Executes one CPU cycle, returning the new state.
 *
 * @param   {CPU} state
 * @returns {CPU}
 */
export function cycle(state) {
	var receiver;
	const phase = state.next;
	switch (true) {
		case state.rst:       receiver = create; break;
		case phase in phases: receiver = phases[phase]; break;
		default:              return { ...state };
	}
	return { ...state, phase, ...receiver(state) };
}

/**
 * Handlers for every phase the CPU can transition to.
 */
export const phases = {
	FETCH, FETCH2,
	OPR, CLR, NOT, INC, ROL, ROR,
	LD, LD2, ST,
	ADD, ADD2,
	AND, AND2,
	JMP, JZ,
	OUT,
};

/**
 * A description of an opcode.
 *
 * @typedef  {Object}  Opcode
 * @property {Number}  code     - Unique code representing this opcode
 * @property {String}  mnemonic - Next phase to be executed
 * @property {String}  phase    - CPU phase that handles this opcode
 * @property {Array}   operands - Data types for all accepted operands
 */

/**
 * Specifications for all opcodes supported by the CPU.
 *
 * | Opcode | Mnemonic | Operands | T | Function                            |
 * |--------|----------|----------|---|-------------------------------------|
 * | 000    | OPR      | operator | 1 | Operate on accumulator              |
 * | 001    | LD       | address  | 2 | Load memory into accumulator        |
 * | 010    | ST       | address  | 1 | Store accumulator into memory       |
 * | 011    | ADD      | address  | 2 | Add memory location to accumulator  |
 * | 100    | AND      | address  | 2 | Bitwise AND memory with accumulator |
 * | 101    | JMP      | address  | 1 | Inconditional jump                  |
 * | 110    | JZ       | address  | 1 | Jump if accumulator is zero         |
 * | 111    | OUT      |          | 1 | Output accumulator                  |
 */
export const opcodes = [
	{ code:0b000, mnemonic:'OPR', phase:'OPR', operands:['operator'] },
	{ code:0b001, mnemonic:'LD',  phase:'LD',  operands:['address']  },
	{ code:0b010, mnemonic:'ST',  phase:'ST',  operands:['address']  },
	{ code:0b011, mnemonic:'ADD', phase:'ADD', operands:['address']  },
	{ code:0b100, mnemonic:'AND', phase:'AND', operands:['address']  },
	{ code:0b101, mnemonic:'JMP', phase:'JMP', operands:['address']  },
	{ code:0b110, mnemonic:'JZ',  phase:'JZ',  operands:['address']  },
	{ code:0b111, mnemonic:'OUT', phase:'OUT', operands:[]           },
];

/**
 * A map between the operators supported by the OPR instruction and the
 * phase transitions that handle their execution. All operators whose
 * flag is expressed will run in the same cycle and in the same order as
 * they are presented here.
 *
 * | Operator | Mnemonic | Function                     |
 * |----------|----------|------------------------------|
 * | 00001    | CLR      | Clear accumulator            |
 * | 00010    | NOT      | One's complement accumulator |
 * | 00100    | INC      | Increment accumulator        |
 * | 01000    | ROL      | Rotate accumulator left      |
 * | 10000    | ROR      | Rotate accumulator right     |
 */
export const operators = {
	0b00001: 'CLR',
	0b00010: 'NOT',
	0b00100: 'INC',
	0b01000: 'ROL',
	0b10000: 'ROR',
};

/**
 * Decodes a word into into opcode, operand and phase transition.
 *
 * Produces values for the instruction and address register, and the cpu
 * phase to transition to in order to execute the opcode.
 *
 * @method  decode
 * @param   {Number} word
 * @returns {Object}
 */
export function decode(word) {
	const ir = (word & 0b11100000) >> 5;
	const ar = (word & 0b00011111) >> 0;
	const opcode = opcodes[ir];
	return { ir, ar, opcode };
}

/**
 * Fetch the instruction at PC.
 *
 * Points the address register to the program counter, raises the read
 * request flag, and waits for memory to reply.
 *
 * @param   {CPU} state
 * @returns {CPU}
 */
export function FETCH(state) {
	return {
		ar: state.pc,
		read: true,
		write: false,
		next: 'FETCH2',
	};
}

/**
 * Second step of the instruction fetch phase.
 *
 * Reads the value arriving from memory on the data register and writes
 * it into the instruction and address registers, then increments the
 * program counter. Will transition to the handler for the opcode
 * expressed by the instruction register.
 *
 * @param   {CPU} state
 * @returns {CPU}
 */
export function FETCH2(state) {
	const {opcode, ir, ar} = decode(state.dr);
	const next = opcode.phase;
	return {
		ir, ar,
		read: false,
		pc: state.pc + 1,
		next,
	};
}

/**
 * Load memory into accumulator.
 *
 * Points the address register to the memory location to be read, raises
 * the read request flag, and waits for memory to reply.
 *
 * @param   {CPU} state
 * @returns {CPU}
 */
function LD(state) {
	return {
		ar: state.ar,
		read: true,
		next: 'LD2',
	};
}

/**
 * Second step of the load operation.
 *
 * Reads the value arriving from memory on the data register and writes
 * it into the accumulator.
 *
 * @param   {CPU} state
 * @returns {CPU}
 */
function LD2(state) {
	return {
		a: state.dr,
		read: false,
		next: 'FETCH',
	};
}

/**
 * Store accumulator into memory.
 *
 * Points the address register to the memory location to be written,
 * writes the contents of the accumulator on the data register, and
 * triggers the write request flag.
 *
 * @param   {CPU} state
 * @returns {CPU}
 */
function ST(state) {
	return {
		ar: state.ar,
		dr: state.a,
		write: true,
		next: 'FETCH',
	};
}

/**
 * Add memory location to accumulator.
 *
 * Points the address register to the memory location of the operand,
 * raises the read request flag, and waits for memory to reply.
 *
 * @param   {CPU} state
 * @returns {CPU}
 */
function ADD(state) {
	return {
		ar: state.ar,
		read: true,
		next: 'ADD2',
	};
}

/**
 * Second step of the addition operation.
 *
 * Reads the value arriving from memory on the data register, adds it to
 * the accumulator, and stores the results into the accumulator.
 *
 * @param   {CPU} state
 * @returns {CPU}
 */
function ADD2(state) {
	return {
		a: (state.a + state.dr) % 256,
		read: false,
		next: 'FETCH',
	};
}

/**
 * Bitwise AND memory with accumulator.
 *
 * Points the address register to the memory location of the operand,
 * raises the read request flag, and waits for memory to reply.
 *
 * @param   {CPU} state
 * @returns {CPU}
 */
function AND(state) {
	return {
		read: true,
		next: 'AND2',
	};
}

/**
 * Second step of the bitwise AND operation.
 *
 * Reads the value arriving from memory on the data register, ANDs it
 * with the accumulator, and stores the results into the accumulator.
 *
 * @param   {CPU} state
 * @returns {CPU}
 */
function AND2(state) {
	return {
		a: (state.a & state.dr) % 256,
		read: false,
		next: 'FETCH',
	};
}

/**
 * Inconditional jump.
 *
 * Points the program counter to the address in the address register.
 *
 * @param   {CPU} state
 * @returns {CPU}
 */
function JMP(state) {
	return {
		pc: state.ar & 0b00011111,
		next: 'FETCH',
	};
}

/**
 * Jump if accumulator is zero.
 *
 * Points the program counter to the address in the address register if
 * the accumulator is zero.
 *
 * @param   {CPU} state
 * @returns {CPU}
 */
function JZ(state) {
	const z = state.a === 0;
	const ar = state.ar & 0b00011111;
	const pc = z? ar : state.pc;
	return {
		pc,
		next: 'FETCH',
	};
}

/**
 * Output accumulator.
 *
 * Outputs the value of the accumulator to the standard output.
 *
 * @param   {CPU} state
 * @returns {CPU}
 */
function OUT(state) {
	/* eslint-disable */
	// TODO: Implement output device or generic I/O.
	console.log(state.a);
	/* eslint-enable */
	return {
		next: 'FETCH',
	};
}

/**
 * Operate on accumulator.
 *
 * Executes the operation expressed by the address register on the
 * accumulator, and stores the results into the accumulator. Requires no
 * operands.
 *
 * @param   {CPU} state
 * @returns {CPU}
 */
function OPR(state) {
	const newState = Object
		.keys(operators)
		.map(op => parseInt(op, 10)).sort()
		.filter(op => state.ar & op)
		.reduce(function(diff, op) {
			var handler = phases[operators[op]];
			return { ...diff, ...handler({ ...state, ...diff }) };
		}, {});
	newState.next = 'FETCH';
	return newState;
}

/**
 * Clear accumulator.
 *
 * Resets the accumulator to zero.
 *
 * @param   {CPU} state
 * @returns {CPU}
 */
function CLR(state) {
	return {
		a: 0,
	};
}

/**
 * One's complement accumulator.
 *
 * Replaces the value in the accumulator with its one's complement.
 *
 * @param   {CPU} state
 * @returns {CPU}
 */
function NOT(state) {
	return {
		a: ~state.a & 0b11111111,
	};
}

/**
 * Increment accumulator.
 *
 * Increments the value in the accumulator by 1.
 *
 * @param   {CPU} state
 * @returns {CPU}
 */
function INC(state) {
	return {
		a: (state.a + 1) % 256,
	};
}

/**
 * Rotate accumulator left.
 *
 * Rotates the accumulator to the left by 1.
 *
 * @param   {CPU} state
 * @returns {CPU}
 */
function ROL(state) {
	return {
		a: (state.a << 1) % 256,
	};
}

/**
 * Rotate accumulator right.
 *
 * Rotates the accumulator to the right by 1.
 *
 * @param   {CPU} state
 * @returns {CPU}
 */
function ROR(state) {
	return {
		a: (state.a >> 1) % 256,
	};
}

/**
 * Returns an overview of the state of a CPU as text.
 *
 * @param   {CPU} state
 * @returns {String}
 */
export function toString(state) {
	const rst = state.rst? 'on' : 'off';
	const read = state.read? 'on' : 'off';
	const write = state.write? 'on' : 'off';
	const {phase, next, a, dr, ir, ar, pc} = state;
	return `CPU ${phase}->${next} RST:${rst} READ:${read} WRITE:${write} `
	     + `A:${a} AR:${ar} DR:${dr} IR:${ir} PC:${pc}`;
}
<|endoftext|>


<commit_before>import ui.ImageView as ImageView;
import ui.SpriteView as SpriteView;

import .utils;
import .shapes.ShapeFactory as ShapeFactory;

var shapes = ShapeFactory.get();
var readOnlyProp = utils.addReadOnlyProperty;

exports = Class(SpriteView, function () {
  var supr = SpriteView.prototype;

  this.name = "EntityView";
  this._debugDraw = false;

  this.init = function (opts) {
    opts.tag = opts.tag || opts.entity.uid;
    this._entity = opts.entity;

    this._validateSprite(opts);
    if (!this.isSprite) {
      opts.image = opts.image || opts.url;
    }

    supr.init.call(this, opts);
  };

  this.reset = function (opts) {
    var viewOpts = opts.viewOpts = opts.viewOpts || opts;
    shapes.applyImageDimensions(viewOpts);

    this.clipRect = opts.clipRect;

    var model = this._entity.model;
    viewOpts.offsetX = viewOpts.offsetX || opts.offsetX || 0;
    viewOpts.offsetY = viewOpts.offsetY || opts.offsetY || 0;
    viewOpts.width = viewOpts.width || opts.width || 0;
    viewOpts.height = viewOpts.height || opts.height || 0;
    viewOpts.anchorX = viewOpts.anchorX !== undefined ? viewOpts.anchorX : viewOpts.width / 2;
    viewOpts.anchorY = viewOpts.anchorY !== undefined ? viewOpts.anchorY : viewOpts.height / 2;
    this.resetAllAnimations(viewOpts);
    this.updateOpts(viewOpts);

    var s = this.style;
    s.x = model.x;
    s.y = model.y;
    s.visible = true;
  };

  this.update = function (dt) {
    var model = this._entity.model;
    var s = this.style;
    s.x = model.x;
    s.y = model.y;
  };

  /**
   * SpriteView Extensions
   */

  this.resetAllAnimations = function (opts) {
    this._validateSprite(opts);

    if (this.isSprite) {
      supr.resetAllAnimations.call(this, opts);
      this.setImage(this._animations[opts.defaultAnimation].frames[0]);
    } else {
      this.setImage(opts.image || opts.url);
    }
  };

  this.startAnimation = function (name, opts) {
    if (this.isSprite && this._animations[name]) {
      supr.startAnimation.call(this, name, opts);
    }
  };

  this._validateSprite = function(opts) {
    this.isSprite = !!SpriteView.allAnimations[opts.url];
  };

  /**
   * Helpers
   */

  readOnlyProp(this, 'minX', function () {
   var s = this.style;
   return s.x + s.offsetX;
  });

  readOnlyProp(this, 'maxX', function () {
   var s = this.style;
   return s.x + s.offsetX + s.width;
  });

  readOnlyProp(this, 'minY', function () {
   var s = this.style;
   return s.y + s.offsetY;
  });

  readOnlyProp(this, 'maxY', function () {
   var s = this.style;
   return s.y + s.offsetY + s.height;
  });

  Object.defineProperty(this, 'width', {
    enumerable: true,
    configurable: true,
    get: function () { return this.style.width; },
    set: function (value) { this.style.width = value; }
  });

  Object.defineProperty(this, 'height', {
    enumerable: true,
    configurable: true,
    get: function () { return this.style.height; },
    set: function (value) { this.style.height = value; }
  });

  Object.defineProperty(this, 'visible', {
    enumerable: true,
    configurable: true,
    get: function () { return this.style.visible; },
    set: function (value) { this.style.visible = value; }
  });

  /**
   * Debugging Utilities
   */

  this.showHitBounds = function () {
    this._debugDraw = true;
  };

  this.hideHitBounds = function () {
    this._debugDraw = false;
  };

  this.render = function (ctx) {

    if (this.clipRect) {
      ctx.save();
      ctx.clipRect(
        this.clipRect.x - this.style.offsetX,
        this.clipRect.y - this.style.offsetY,
        this.clipRect.width,
        this.clipRect.height
      );
    }

    supr.render.call(this, ctx);

    if (this.clipRect) { ctx.restore(); }

    if (this._debugDraw) {
      ctx.save();

      // remove flips if necessary
      if (this.style.flipX || this.style.flipY) {
        ctx.translate(
          this.style.flipX ? this.style.width / 2 : 0,
          this.style.flipY ? this.style.height / 2 : 0
        );

        ctx.scale(
          this.style.flipX ? -1 : 1,
          this.style.flipY ? -1 : 1
        );

        ctx.translate(
          this.style.flipX ? -this.style.width / 2 : 0,
          this.style.flipY ? -this.style.height / 2 : 0
        );
      }

      // remove offsets and scale
      var invScale = 1 / this.style.scale;
      ctx.translate(-this.minX, -this.minY);
      ctx.scale(invScale, invScale);

      // draw debug lines
      var model = this._entity.model;
      var shape = model.shape;
      ctx.fillStyle = 'rgba(255, 0, 0, 0.5)';
      if (shape.radius) {
        ctx.beginPath();
        ctx.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI, false);
        ctx.fill();
      } else if (shape.width && shape.height) {
        ctx.fillRect(shape.x, shape.y, shape.width, shape.height);
      }

      ctx.restore();
    }
  };

});
<commit_msg>Defaulting clipRect to null<commit_after>import ui.ImageView as ImageView;
import ui.SpriteView as SpriteView;

import .utils;
import .shapes.ShapeFactory as ShapeFactory;

var shapes = ShapeFactory.get();
var readOnlyProp = utils.addReadOnlyProperty;

exports = Class(SpriteView, function () {
  var supr = SpriteView.prototype;

  this.name = "EntityView";
  this._debugDraw = false;

  this.init = function (opts) {
    opts.tag = opts.tag || opts.entity.uid;
    this._entity = opts.entity;

    this._validateSprite(opts);
    if (!this.isSprite) {
      opts.image = opts.image || opts.url;
    }

    supr.init.call(this, opts);
  };

  this.reset = function (opts) {
    var viewOpts = opts.viewOpts = opts.viewOpts || opts;
    shapes.applyImageDimensions(viewOpts);

    this.clipRect = opts.clipRect || null;

    var model = this._entity.model;
    viewOpts.offsetX = viewOpts.offsetX || opts.offsetX || 0;
    viewOpts.offsetY = viewOpts.offsetY || opts.offsetY || 0;
    viewOpts.width = viewOpts.width || opts.width || 0;
    viewOpts.height = viewOpts.height || opts.height || 0;
    viewOpts.anchorX = viewOpts.anchorX !== undefined ? viewOpts.anchorX : viewOpts.width / 2;
    viewOpts.anchorY = viewOpts.anchorY !== undefined ? viewOpts.anchorY : viewOpts.height / 2;
    this.resetAllAnimations(viewOpts);
    this.updateOpts(viewOpts);

    var s = this.style;
    s.x = model.x;
    s.y = model.y;
    s.visible = true;
  };

  this.update = function (dt) {
    var model = this._entity.model;
    var s = this.style;
    s.x = model.x;
    s.y = model.y;
  };

  /**
   * SpriteView Extensions
   */

  this.resetAllAnimations = function (opts) {
    this._validateSprite(opts);

    if (this.isSprite) {
      supr.resetAllAnimations.call(this, opts);
      this.setImage(this._animations[opts.defaultAnimation].frames[0]);
    } else {
      this.setImage(opts.image || opts.url);
    }
  };

  this.startAnimation = function (name, opts) {
    if (this.isSprite && this._animations[name]) {
      supr.startAnimation.call(this, name, opts);
    }
  };

  this._validateSprite = function(opts) {
    this.isSprite = !!SpriteView.allAnimations[opts.url];
  };

  /**
   * Helpers
   */

  readOnlyProp(this, 'minX', function () {
   var s = this.style;
   return s.x + s.offsetX;
  });

  readOnlyProp(this, 'maxX', function () {
   var s = this.style;
   return s.x + s.offsetX + s.width;
  });

  readOnlyProp(this, 'minY', function () {
   var s = this.style;
   return s.y + s.offsetY;
  });

  readOnlyProp(this, 'maxY', function () {
   var s = this.style;
   return s.y + s.offsetY + s.height;
  });

  Object.defineProperty(this, 'width', {
    enumerable: true,
    configurable: true,
    get: function () { return this.style.width; },
    set: function (value) { this.style.width = value; }
  });

  Object.defineProperty(this, 'height', {
    enumerable: true,
    configurable: true,
    get: function () { return this.style.height; },
    set: function (value) { this.style.height = value; }
  });

  Object.defineProperty(this, 'visible', {
    enumerable: true,
    configurable: true,
    get: function () { return this.style.visible; },
    set: function (value) { this.style.visible = value; }
  });

  /**
   * Debugging Utilities
   */

  this.showHitBounds = function () {
    this._debugDraw = true;
  };

  this.hideHitBounds = function () {
    this._debugDraw = false;
  };

  this.render = function (ctx) {

    if (this.clipRect) {
      ctx.save();
      ctx.clipRect(
        this.clipRect.x - this.style.offsetX,
        this.clipRect.y - this.style.offsetY,
        this.clipRect.width,
        this.clipRect.height
      );
    }

    supr.render.call(this, ctx);

    if (this.clipRect) { ctx.restore(); }

    if (this._debugDraw) {
      ctx.save();

      // remove flips if necessary
      if (this.style.flipX || this.style.flipY) {
        ctx.translate(
          this.style.flipX ? this.style.width / 2 : 0,
          this.style.flipY ? this.style.height / 2 : 0
        );

        ctx.scale(
          this.style.flipX ? -1 : 1,
          this.style.flipY ? -1 : 1
        );

        ctx.translate(
          this.style.flipX ? -this.style.width / 2 : 0,
          this.style.flipY ? -this.style.height / 2 : 0
        );
      }

      // remove offsets and scale
      var invScale = 1 / this.style.scale;
      ctx.translate(-this.minX, -this.minY);
      ctx.scale(invScale, invScale);

      // draw debug lines
      var model = this._entity.model;
      var shape = model.shape;
      ctx.fillStyle = 'rgba(255, 0, 0, 0.5)';
      if (shape.radius) {
        ctx.beginPath();
        ctx.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI, false);
        ctx.fill();
      } else if (shape.width && shape.height) {
        ctx.fillRect(shape.x, shape.y, shape.width, shape.height);
      }

      ctx.restore();
    }
  };

});
<|endoftext|>