/*
 * Copyright 2023 WebAssembly Community Group participants
 *
 * 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.
 */

#ifndef wasm_analysis_lattice_h
#define wasm_analysis_lattice_h

#if __cplusplus >= 202002L
#include <concepts>
#endif // __cplusplus >= 202002L

namespace wasm::analysis {

enum LatticeComparison { NO_RELATION, EQUAL, LESS, GREATER };

// If parameter "comparison" compares x and y, the function returns the opposite
// direction comparison between y and x.
inline LatticeComparison reverseComparison(LatticeComparison comparison) {
  if (comparison == LatticeComparison::LESS) {
    return LatticeComparison::GREATER;
  } else if (comparison == LatticeComparison::GREATER) {
    return LatticeComparison::LESS;
  } else {
    return comparison;
  }
}

#if __cplusplus >= 202002L

template<typename L>
concept Lattice = requires(const L& lattice,
                           const typename L::Element& constElem,
                           typename L::Element& elem) {
  // Lattices must have elements.
  typename L::Element;
  requires std::copyable<typename L::Element>;
  // Get the bottom element of this lattice.
  { lattice.getBottom() } noexcept -> std::same_as<typename L::Element>;
  // Compare two elements of this lattice. TODO: use <=> and
  // std::three_way_comparable once we support C++20 everywhere.
  {
    lattice.compare(constElem, constElem)
  } noexcept -> std::same_as<LatticeComparison>;
  // Modify `elem` in-place to be the join (aka least upper bound) of `elem` and
  // `constElem`, returning true iff `elem` was modified, i.e. if it was not
  // already an upper bound of `constElem`.
  { lattice.join(elem, constElem) } noexcept -> std::same_as<bool>;
};

// The analysis framework only uses bottom elements and least upper bounds (i.e.
// joins) directly, so lattices do not necessarily need to implement top
// elements and greatest lower bounds (i.e. meets) to be useable, even though
// they are required for mathematical lattices. Implementing top elements and
// meets does have the benefit of making a lattice generically invertable,
// though. See lattices/inverted.h.
template<typename L>
concept FullLattice =
  Lattice<L> && requires(const L& lattice,
                         const typename L::Element& constElem,
                         typename L::Element& elem) {
    // Get the top element of this lattice.
    { lattice.getTop() } noexcept -> std::same_as<typename L::Element>;
    // Modify `elem` in-place to be the meet (aka greatest lower bound) of
    // `elem` and `constEleme`, returning true iff `elem` was modified, i.e. if
    // it was not already a lower bound of `constElem`.
    { lattice.meet(elem, constElem) } noexcept -> std::same_as<bool>;
  };

#else // __cplusplus >= 202002L

#define Lattice typename
#define FullLattice typename

#endif // __cplusplus >= 202002L

} // namespace wasm::analysis

#endif // wasm_analysis_lattice_h