// Generated by generate-monoidal-reducer.js
/**
 * Copyright 2018 Shape Security, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

const Shift = require('shift-ast');

module.exports = class MonoidalReducer {
  constructor(monoid) {
    let identity = monoid.empty();
    this.identity = identity;

    let concatThunk;
    if (monoid.prototype && typeof monoid.prototype.concatThunk === 'function') {
      concatThunk = Function.prototype.call.bind(monoid.prototype.concatThunk);
    } else if (typeof monoid.concatThunk === 'function') {
      concatThunk = monoid.concatThunk;
    } else {
      let concat;
      if (monoid.prototype && typeof monoid.prototype.concat === 'function') {
        concat = Function.prototype.call.bind(monoid.prototype.concat);
      } else if (typeof monoid.concat === 'function') {
        concat = monoid.concat;
      } else {
        throw new TypeError('Monoid must provide a `concatThunk` or `concat` method');
      }
      if (typeof monoid.isAbsorbing === 'function') {
        let isAbsorbing = monoid.isAbsorbing;
        concatThunk = (a, b) => isAbsorbing(a) ? a : concat(a, b());
      } else {
        concatThunk = (a, b) => concat(a, b());
      }
    }
    this.append = (...args) => args.reduce(concatThunk, identity);
  }

  reduceArrayAssignmentTarget(node, { elements, rest }) {
    return this.append(...elements.filter(n => n != null), rest == null ? () => this.identity : rest);
  }

  reduceArrayBinding(node, { elements, rest }) {
    return this.append(...elements.filter(n => n != null), rest == null ? () => this.identity : rest);
  }

  reduceArrayExpression(node, { elements }) {
    return this.append(...elements.filter(n => n != null));
  }

  reduceArrowExpression(node, { params, body }) {
    return this.append(params, body);
  }

  reduceAssignmentExpression(node, { binding, expression }) {
    return this.append(binding, expression);
  }

  reduceAssignmentTargetIdentifier(node) {
    return this.identity;
  }

  reduceAssignmentTargetPropertyIdentifier(node, { binding, init }) {
    return this.append(binding, init == null ? () => this.identity : init);
  }

  reduceAssignmentTargetPropertyProperty(node, { name, binding }) {
    return this.append(name, binding);
  }

  reduceAssignmentTargetWithDefault(node, { binding, init }) {
    return this.append(binding, init);
  }

  reduceAwaitExpression(node, { expression }) {
    return expression();
  }

  reduceBinaryExpression(node, { left, right }) {
    return this.append(left, right);
  }

  reduceBindingIdentifier(node) {
    return this.identity;
  }

  reduceBindingPropertyIdentifier(node, { binding, init }) {
    return this.append(binding, init == null ? () => this.identity : init);
  }

  reduceBindingPropertyProperty(node, { name, binding }) {
    return this.append(name, binding);
  }

  reduceBindingWithDefault(node, { binding, init }) {
    return this.append(binding, init);
  }

  reduceBlock(node, { statements }) {
    return this.append(...statements);
  }

  reduceBlockStatement(node, { block }) {
    return block();
  }

  reduceBreakStatement(node) {
    return this.identity;
  }

  reduceCallExpression(node, { callee, arguments: _arguments }) {
    return this.append(callee, ..._arguments);
  }

  reduceCatchClause(node, { binding, body }) {
    return this.append(binding == null ? () => this.identity : binding, body);
  }

  reduceClassDeclaration(node, { name, super: _super, elements }) {
    return this.append(name, _super == null ? () => this.identity : _super, ...elements);
  }

  reduceClassElement(node, { method }) {
    return method();
  }

  reduceClassExpression(node, { name, super: _super, elements }) {
    return this.append(name == null ? () => this.identity : name, _super == null ? () => this.identity : _super, ...elements);
  }

  reduceCompoundAssignmentExpression(node, { binding, expression }) {
    return this.append(binding, expression);
  }

  reduceComputedMemberAssignmentTarget(node, { object, expression }) {
    return this.append(object, expression);
  }

  reduceComputedMemberExpression(node, { object, expression }) {
    return this.append(object, expression);
  }

  reduceComputedPropertyName(node, { expression }) {
    return expression();
  }

  reduceConditionalExpression(node, { test, consequent, alternate }) {
    return this.append(test, consequent, alternate);
  }

  reduceContinueStatement(node) {
    return this.identity;
  }

  reduceDataProperty(node, { name, expression }) {
    return this.append(name, expression);
  }

  reduceDebuggerStatement(node) {
    return this.identity;
  }

  reduceDirective(node) {
    return this.identity;
  }

  reduceDoWhileStatement(node, { body, test }) {
    return this.append(body, test);
  }

  reduceEmptyStatement(node) {
    return this.identity;
  }

  reduceExport(node, { declaration }) {
    return declaration();
  }

  reduceExportAllFrom(node) {
    return this.identity;
  }

  reduceExportDefault(node, { body }) {
    return body();
  }

  reduceExportFrom(node, { namedExports }) {
    return this.append(...namedExports);
  }

  reduceExportFromSpecifier(node) {
    return this.identity;
  }

  reduceExportLocalSpecifier(node, { name }) {
    return name();
  }

  reduceExportLocals(node, { namedExports }) {
    return this.append(...namedExports);
  }

  reduceExpressionStatement(node, { expression }) {
    return expression();
  }

  reduceForAwaitStatement(node, { left, right, body }) {
    return this.append(left, right, body);
  }

  reduceForInStatement(node, { left, right, body }) {
    return this.append(left, right, body);
  }

  reduceForOfStatement(node, { left, right, body }) {
    return this.append(left, right, body);
  }

  reduceForStatement(node, { init, test, update, body }) {
    return this.append(init == null ? () => this.identity : init, test == null ? () => this.identity : test, update == null ? () => this.identity : update, body);
  }

  reduceFormalParameters(node, { items, rest }) {
    return this.append(...items, rest == null ? () => this.identity : rest);
  }

  reduceFunctionBody(node, { directives, statements }) {
    return this.append(...directives, ...statements);
  }

  reduceFunctionDeclaration(node, { name, params, body }) {
    return this.append(name, params, body);
  }

  reduceFunctionExpression(node, { name, params, body }) {
    return this.append(name == null ? () => this.identity : name, params, body);
  }

  reduceGetter(node, { name, body }) {
    return this.append(name, body);
  }

  reduceIdentifierExpression(node) {
    return this.identity;
  }

  reduceIfStatement(node, { test, consequent, alternate }) {
    return this.append(test, consequent, alternate == null ? () => this.identity : alternate);
  }

  reduceImport(node, { defaultBinding, namedImports }) {
    return this.append(defaultBinding == null ? () => this.identity : defaultBinding, ...namedImports);
  }

  reduceImportNamespace(node, { defaultBinding, namespaceBinding }) {
    return this.append(defaultBinding == null ? () => this.identity : defaultBinding, namespaceBinding);
  }

  reduceImportSpecifier(node, { binding }) {
    return binding();
  }

  reduceLabeledStatement(node, { body }) {
    return body();
  }

  reduceLiteralBooleanExpression(node) {
    return this.identity;
  }

  reduceLiteralInfinityExpression(node) {
    return this.identity;
  }

  reduceLiteralNullExpression(node) {
    return this.identity;
  }

  reduceLiteralNumericExpression(node) {
    return this.identity;
  }

  reduceLiteralRegExpExpression(node) {
    return this.identity;
  }

  reduceLiteralStringExpression(node) {
    return this.identity;
  }

  reduceMethod(node, { name, params, body }) {
    return this.append(name, params, body);
  }

  reduceModule(node, { directives, items }) {
    return this.append(...directives, ...items);
  }

  reduceNewExpression(node, { callee, arguments: _arguments }) {
    return this.append(callee, ..._arguments);
  }

  reduceNewTargetExpression(node) {
    return this.identity;
  }

  reduceObjectAssignmentTarget(node, { properties, rest }) {
    return this.append(...properties, rest == null ? () => this.identity : rest);
  }

  reduceObjectBinding(node, { properties, rest }) {
    return this.append(...properties, rest == null ? () => this.identity : rest);
  }

  reduceObjectExpression(node, { properties }) {
    return this.append(...properties);
  }

  reduceReturnStatement(node, { expression }) {
    return expression == null ? this.identity : expression();
  }

  reduceScript(node, { directives, statements }) {
    return this.append(...directives, ...statements);
  }

  reduceSetter(node, { name, param, body }) {
    return this.append(name, param, body);
  }

  reduceShorthandProperty(node, { name }) {
    return name();
  }

  reduceSpreadElement(node, { expression }) {
    return expression();
  }

  reduceSpreadProperty(node, { expression }) {
    return expression();
  }

  reduceStaticMemberAssignmentTarget(node, { object }) {
    return object();
  }

  reduceStaticMemberExpression(node, { object }) {
    return object();
  }

  reduceStaticPropertyName(node) {
    return this.identity;
  }

  reduceSuper(node) {
    return this.identity;
  }

  reduceSwitchCase(node, { test, consequent }) {
    return this.append(test, ...consequent);
  }

  reduceSwitchDefault(node, { consequent }) {
    return this.append(...consequent);
  }

  reduceSwitchStatement(node, { discriminant, cases }) {
    return this.append(discriminant, ...cases);
  }

  reduceSwitchStatementWithDefault(node, { discriminant, preDefaultCases, defaultCase, postDefaultCases }) {
    return this.append(discriminant, ...preDefaultCases, defaultCase, ...postDefaultCases);
  }

  reduceTemplateElement(node) {
    return this.identity;
  }

  reduceTemplateExpression(node, { tag, elements }) {
    return this.append(tag == null ? () => this.identity : tag, ...elements);
  }

  reduceThisExpression(node) {
    return this.identity;
  }

  reduceThrowStatement(node, { expression }) {
    return expression();
  }

  reduceTryCatchStatement(node, { body, catchClause }) {
    return this.append(body, catchClause);
  }

  reduceTryFinallyStatement(node, { body, catchClause, finalizer }) {
    return this.append(body, catchClause == null ? () => this.identity : catchClause, finalizer);
  }

  reduceUnaryExpression(node, { operand }) {
    return operand();
  }

  reduceUpdateExpression(node, { operand }) {
    return operand();
  }

  reduceVariableDeclaration(node, { declarators }) {
    return this.append(...declarators);
  }

  reduceVariableDeclarationStatement(node, { declaration }) {
    return declaration();
  }

  reduceVariableDeclarator(node, { binding, init }) {
    return this.append(binding, init == null ? () => this.identity : init);
  }

  reduceWhileStatement(node, { test, body }) {
    return this.append(test, body);
  }

  reduceWithStatement(node, { object, body }) {
    return this.append(object, body);
  }

  reduceYieldExpression(node, { expression }) {
    return expression == null ? this.identity : expression();
  }

  reduceYieldGeneratorExpression(node, { expression }) {
    return expression();
  }
};
