//===-- StatementContext.h --------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// Coding style: https://mlir.llvm.org/getting_started/DeveloperGuide/
//
//===----------------------------------------------------------------------===//

#ifndef FORTRAN_LOWER_STATEMENTCONTEXT_H
#define FORTRAN_LOWER_STATEMENTCONTEXT_H

#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallVector.h"
#include <functional>
#include <optional>

namespace Fortran::lower {

/// When lowering a statement, temporaries for intermediate results may be
/// allocated on the heap. A StatementContext enables their deallocation
/// with one of several explicit finalize calls, or with an implicit
/// call to finalizeAndPop() at the end of the context. A context may prohibit
/// temporary allocation. Otherwise, an initial "outer" context scope may have
/// nested context scopes, which must make explicit subscope finalize calls.
///
/// In addition to being useful for individual action statement contexts, a
/// StatementContext is also useful for construct blocks delimited by a pair
/// of statements such as (block-stmt, end-block-stmt), or a program unit
/// delimited by a pair of statements such as (subroutine-stmt, end-subroutine-
/// stmt). Attached cleanup code for these contexts may include stack
/// management code, deallocation code, and finalization of derived type
/// entities in the context.
class StatementContext {
public:
  explicit StatementContext(bool cleanupProhibited = false) {
    if (cleanupProhibited)
      return;
    cufs.push_back({});
  }

  ~StatementContext() {
    if (!cufs.empty())
      finalizeAndPop();
    assert(cufs.empty() && "invalid StatementContext destructor call");
  }

  using CleanupFunction = std::function<void()>;

  /// Push a context subscope.
  void pushScope() {
    assert(!cufs.empty() && "invalid pushScope statement context");
    cufs.push_back({});
  }

  /// Append a cleanup function to the "list" of cleanup functions.
  void attachCleanup(CleanupFunction cuf) {
    assert(!cufs.empty() && "invalid attachCleanup statement context");
    if (cufs.back()) {
      CleanupFunction oldCleanup = *cufs.back();
      cufs.back() = [=]() {
        cuf();
        oldCleanup();
      };
    } else {
      cufs.back() = cuf;
    }
  }

  /// Make cleanup calls. Retain the stack top list for a repeat call.
  void finalizeAndKeep() {
    assert(!cufs.empty() && "invalid finalize statement context");
    if (cufs.back())
      (*cufs.back())();
  }

  /// Make cleanup calls. Clear the stack top list.
  void finalizeAndReset() {
    finalizeAndKeep();
    cufs.back().reset();
  }

  /// Make cleanup calls. Pop the stack top list.
  void finalizeAndPop() {
    finalizeAndKeep();
    cufs.pop_back();
  }

  bool hasCode() const {
    return !cufs.empty() && llvm::any_of(cufs, [](auto &opt) -> bool {
      return opt.has_value();
    });
  }

private:
  // A statement context should never be copied or moved.
  StatementContext(const StatementContext &) = delete;
  StatementContext &operator=(const StatementContext &) = delete;
  StatementContext(StatementContext &&) = delete;

  // Stack of cleanup function "lists" (nested cleanup function calls).
  llvm::SmallVector<std::optional<CleanupFunction>> cufs;
};

} // namespace Fortran::lower

#endif // FORTRAN_LOWER_STATEMENTCONTEXT_H
