/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 * vim: set ts=8 sts=4 et sw=4 tw=99:
 *
 * Copyright 2015 Mozilla Foundation
 *
 * 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 wasmast_h
#define wasmast_h

#include "ds/LifoAlloc.h"
#include "js/HashTable.h"
#include "js/Vector.h"
#include "wasm/WasmTypes.h"

namespace js {
namespace wasm {

const uint32_t AstNoIndex = UINT32_MAX;
const unsigned AST_LIFO_DEFAULT_CHUNK_SIZE = 4096;

/*****************************************************************************/
// wasm AST

class AstExpr;

template <class T>
using AstVector = mozilla::Vector<T, 0, LifoAllocPolicy<Fallible>>;

template <class K, class V, class HP>
using AstHashMap = HashMap<K, V, HP, LifoAllocPolicy<Fallible>>;

class AstName
{
    const char16_t* begin_;
    const char16_t* end_;
  public:
    template <size_t Length>
    explicit AstName(const char16_t (&str)[Length]) : begin_(str), end_(str + Length - 1) {
      MOZ_ASSERT(str[Length - 1] == u'\0');
    }

    AstName(const char16_t* begin, size_t length) : begin_(begin), end_(begin + length) {}
    AstName() : begin_(nullptr), end_(nullptr) {}
    const char16_t* begin() const { return begin_; }
    const char16_t* end() const { return end_; }
    size_t length() const { return end_ - begin_; }
    bool empty() const { return begin_ == nullptr; }

    bool operator==(AstName rhs) const {
        if (length() != rhs.length())
            return false;
        if (begin() == rhs.begin())
            return true;
        return EqualChars(begin(), rhs.begin(), length());
    }
    bool operator!=(AstName rhs) const {
        return !(*this == rhs);
    }
};

class AstRef
{
    AstName name_;
    uint32_t index_;

  public:
    AstRef() : index_(AstNoIndex) {
        MOZ_ASSERT(isInvalid());
    }
    explicit AstRef(AstName name) : name_(name), index_(AstNoIndex) {
        MOZ_ASSERT(!isInvalid());
    }
    explicit AstRef(uint32_t index) : index_(index) {
        MOZ_ASSERT(!isInvalid());
    }
    bool isInvalid() const {
        return name_.empty() && index_ == AstNoIndex;
    }
    AstName name() const {
        return name_;
    }
    size_t index() const {
        MOZ_ASSERT(index_ != AstNoIndex);
        return index_;
    }
    void setIndex(uint32_t index) {
        MOZ_ASSERT(index_ == AstNoIndex);
        index_ = index;
    }
};

struct AstNameHasher
{
    typedef const AstName Lookup;
    static js::HashNumber hash(Lookup l) {
        return mozilla::HashString(l.begin(), l.length());
    }
    static bool match(const AstName key, Lookup lookup) {
        return key == lookup;
    }
};

using AstNameMap = AstHashMap<AstName, uint32_t, AstNameHasher>;

typedef AstVector<ValType> AstValTypeVector;
typedef AstVector<AstExpr*> AstExprVector;
typedef AstVector<AstName> AstNameVector;
typedef AstVector<AstRef> AstRefVector;

struct AstBase
{
    void* operator new(size_t numBytes, LifoAlloc& astLifo) throw() {
        return astLifo.alloc(numBytes);
    }
};

class AstSig : public AstBase
{
    AstName name_;
    AstValTypeVector args_;
    ExprType ret_;

  public:
    explicit AstSig(LifoAlloc& lifo)
      : args_(lifo),
        ret_(ExprType::Void)
    {}
    AstSig(AstValTypeVector&& args, ExprType ret)
      : args_(Move(args)),
        ret_(ret)
    {}
    AstSig(AstName name, AstSig&& rhs)
      : name_(name),
        args_(Move(rhs.args_)),
        ret_(rhs.ret_)
    {}
    const AstValTypeVector& args() const {
        return args_;
    }
    ExprType ret() const {
        return ret_;
    }
    AstName name() const {
        return name_;
    }
    bool operator==(const AstSig& rhs) const {
        return ret() == rhs.ret() && EqualContainers(args(), rhs.args());
    }

    typedef const AstSig& Lookup;
    static HashNumber hash(Lookup sig) {
        return AddContainerToHash(sig.args(), HashNumber(sig.ret()));
    }
    static bool match(const AstSig* lhs, Lookup rhs) {
        return *lhs == rhs;
    }
};

const uint32_t AstNodeUnknownOffset = 0;

class AstNode : public AstBase
{
    uint32_t offset_; // if applicable, offset in the binary format file

  public:
    AstNode() : offset_(AstNodeUnknownOffset) {}

    uint32_t offset() const { return offset_; }
    void setOffset(uint32_t offset) { offset_ = offset; }
};

enum class AstExprKind
{
    BinaryOperator,
    Block,
    Branch,
    BranchTable,
    Call,
    CallIndirect,
    ComparisonOperator,
    Const,
    ConversionOperator,
    CurrentMemory,
    Drop,
    First,
    GetGlobal,
    GetLocal,
    GrowMemory,
    If,
    Load,
    Nop,
    Pop,
    Return,
    SetGlobal,
    SetLocal,
    TeeLocal,
    Store,
    TernaryOperator,
    UnaryOperator,
    Unreachable
};

class AstExpr : public AstNode
{
    const AstExprKind kind_;
    ExprType type_;

  protected:
    AstExpr(AstExprKind kind, ExprType type)
      : kind_(kind), type_(type)
    {}

  public:
    AstExprKind kind() const { return kind_; }

    bool isVoid() const { return IsVoid(type_); }

    // Note that for nodes other than blocks and block-like things, this
    // may return ExprType::Limit for nodes with non-void types.
    ExprType type() const { return type_; }

    template <class T>
    T& as() {
        MOZ_ASSERT(kind() == T::Kind);
        return static_cast<T&>(*this);
    }
};

struct AstNop : AstExpr
{
   static const AstExprKind Kind = AstExprKind::Nop;
   AstNop()
      : AstExpr(AstExprKind::Nop, ExprType::Void)
   {}
};

struct AstUnreachable : AstExpr
{
    static const AstExprKind Kind = AstExprKind::Unreachable;
    AstUnreachable()
      : AstExpr(AstExprKind::Unreachable, ExprType::Void)
    {}
};

class AstDrop : public AstExpr
{
    AstExpr& value_;

  public:
    static const AstExprKind Kind = AstExprKind::Drop;
    explicit AstDrop(AstExpr& value)
      : AstExpr(AstExprKind::Drop, ExprType::Void),
        value_(value)
    {}
    AstExpr& value() const {
        return value_;
    }
};

class AstConst : public AstExpr
{
    const Val val_;

  public:
    static const AstExprKind Kind = AstExprKind::Const;
    explicit AstConst(Val val)
      : AstExpr(Kind, ExprType::Limit),
        val_(val)
    {}
    Val val() const { return val_; }
};

class AstGetLocal : public AstExpr
{
    AstRef local_;

  public:
    static const AstExprKind Kind = AstExprKind::GetLocal;
    explicit AstGetLocal(AstRef local)
      : AstExpr(Kind, ExprType::Limit),
        local_(local)
    {}
    AstRef& local() {
        return local_;
    }
};

class AstSetLocal : public AstExpr
{
    AstRef local_;
    AstExpr& value_;

  public:
    static const AstExprKind Kind = AstExprKind::SetLocal;
    AstSetLocal(AstRef local, AstExpr& value)
      : AstExpr(Kind, ExprType::Void),
        local_(local),
        value_(value)
    {}
    AstRef& local() {
        return local_;
    }
    AstExpr& value() const {
        return value_;
    }
};

class AstGetGlobal : public AstExpr
{
    AstRef global_;

  public:
    static const AstExprKind Kind = AstExprKind::GetGlobal;
    explicit AstGetGlobal(AstRef global)
      : AstExpr(Kind, ExprType::Limit),
        global_(global)
    {}
    AstRef& global() {
        return global_;
    }
};

class AstSetGlobal : public AstExpr
{
    AstRef global_;
    AstExpr& value_;

  public:
    static const AstExprKind Kind = AstExprKind::SetGlobal;
    AstSetGlobal(AstRef global, AstExpr& value)
      : AstExpr(Kind, ExprType::Void),
        global_(global),
        value_(value)
    {}
    AstRef& global() {
        return global_;
    }
    AstExpr& value() const {
        return value_;
    }
};

class AstTeeLocal : public AstExpr
{
    AstRef local_;
    AstExpr& value_;

  public:
    static const AstExprKind Kind = AstExprKind::TeeLocal;
    AstTeeLocal(AstRef local, AstExpr& value)
      : AstExpr(Kind, ExprType::Limit),
        local_(local),
        value_(value)
    {}
    AstRef& local() {
        return local_;
    }
    AstExpr& value() const {
        return value_;
    }
};

class AstBlock : public AstExpr
{
    Op op_;
    AstName name_;
    AstExprVector exprs_;

  public:
    static const AstExprKind Kind = AstExprKind::Block;
    explicit AstBlock(Op op, ExprType type, AstName name, AstExprVector&& exprs)
      : AstExpr(Kind, type),
        op_(op),
        name_(name),
        exprs_(Move(exprs))
    {}

    Op op() const { return op_; }
    AstName name() const { return name_; }
    const AstExprVector& exprs() const { return exprs_; }
};

class AstBranch : public AstExpr
{
    Op op_;
    AstExpr* cond_;
    AstRef target_;
    AstExpr* value_;

  public:
    static const AstExprKind Kind = AstExprKind::Branch;
    explicit AstBranch(Op op, ExprType type,
                       AstExpr* cond, AstRef target, AstExpr* value)
      : AstExpr(Kind, type),
        op_(op),
        cond_(cond),
        target_(target),
        value_(value)
    {}

    Op op() const { return op_; }
    AstRef& target() { return target_; }
    AstExpr& cond() const { MOZ_ASSERT(cond_); return *cond_; }
    AstExpr* maybeValue() const { return value_; }
};

class AstCall : public AstExpr
{
    Op op_;
    AstRef func_;
    AstExprVector args_;

  public:
    static const AstExprKind Kind = AstExprKind::Call;
    AstCall(Op op, ExprType type, AstRef func, AstExprVector&& args)
      : AstExpr(Kind, type), op_(op), func_(func), args_(Move(args))
    {}

    Op op() const { return op_; }
    AstRef& func() { return func_; }
    const AstExprVector& args() const { return args_; }
};

class AstCallIndirect : public AstExpr
{
    AstRef sig_;
    AstExprVector args_;
    AstExpr* index_;

  public:
    static const AstExprKind Kind = AstExprKind::CallIndirect;
    AstCallIndirect(AstRef sig, ExprType type, AstExprVector&& args, AstExpr* index)
      : AstExpr(Kind, type), sig_(sig), args_(Move(args)), index_(index)
    {}
    AstRef& sig() { return sig_; }
    const AstExprVector& args() const { return args_; }
    AstExpr* index() const { return index_; }
};

class AstReturn : public AstExpr
{
    AstExpr* maybeExpr_;

  public:
    static const AstExprKind Kind = AstExprKind::Return;
    explicit AstReturn(AstExpr* maybeExpr)
      : AstExpr(Kind, ExprType::Void),
        maybeExpr_(maybeExpr)
    {}
    AstExpr* maybeExpr() const { return maybeExpr_; }
};

class AstIf : public AstExpr
{
    AstExpr* cond_;
    AstName name_;
    AstExprVector thenExprs_;
    AstExprVector elseExprs_;

  public:
    static const AstExprKind Kind = AstExprKind::If;
    AstIf(ExprType type, AstExpr* cond, AstName name,
          AstExprVector&& thenExprs, AstExprVector&& elseExprs)
      : AstExpr(Kind, type),
        cond_(cond),
        name_(name),
        thenExprs_(Move(thenExprs)),
        elseExprs_(Move(elseExprs))
    {}

    AstExpr& cond() const { return *cond_; }
    const AstExprVector& thenExprs() const { return thenExprs_; }
    bool hasElse() const { return elseExprs_.length(); }
    const AstExprVector& elseExprs() const { MOZ_ASSERT(hasElse()); return elseExprs_; }
    AstName name() const { return name_; }
};

class AstLoadStoreAddress
{
    AstExpr* base_;
    int32_t flags_;
    int32_t offset_;

  public:
    explicit AstLoadStoreAddress(AstExpr* base, int32_t flags, int32_t offset)
      : base_(base),
        flags_(flags),
        offset_(offset)
    {}

    AstExpr& base() const { return *base_; }
    int32_t flags() const { return flags_; }
    int32_t offset() const { return offset_; }
};

class AstLoad : public AstExpr
{
    Op op_;
    AstLoadStoreAddress address_;

  public:
    static const AstExprKind Kind = AstExprKind::Load;
    explicit AstLoad(Op op, const AstLoadStoreAddress &address)
      : AstExpr(Kind, ExprType::Limit),
        op_(op),
        address_(address)
    {}

    Op op() const { return op_; }
    const AstLoadStoreAddress& address() const { return address_; }
};

class AstStore : public AstExpr
{
    Op op_;
    AstLoadStoreAddress address_;
    AstExpr* value_;

  public:
    static const AstExprKind Kind = AstExprKind::Store;
    explicit AstStore(Op op, const AstLoadStoreAddress &address, AstExpr* value)
      : AstExpr(Kind, ExprType::Void),
        op_(op),
        address_(address),
        value_(value)
    {}

    Op op() const { return op_; }
    const AstLoadStoreAddress& address() const { return address_; }
    AstExpr& value() const { return *value_; }
};

class AstCurrentMemory final : public AstExpr
{
  public:
    static const AstExprKind Kind = AstExprKind::CurrentMemory;
    explicit AstCurrentMemory()
      : AstExpr(Kind, ExprType::I32)
    {}
};

class AstGrowMemory final : public AstExpr
{
    AstExpr* operand_;

  public:
    static const AstExprKind Kind = AstExprKind::GrowMemory;
    explicit AstGrowMemory(AstExpr* operand)
      : AstExpr(Kind, ExprType::I32), operand_(operand)
    {}

    AstExpr* operand() const { return operand_; }
};

class AstBranchTable : public AstExpr
{
    AstExpr& index_;
    AstRef default_;
    AstRefVector table_;
    AstExpr* value_;

  public:
    static const AstExprKind Kind = AstExprKind::BranchTable;
    explicit AstBranchTable(AstExpr& index, AstRef def, AstRefVector&& table,
                            AstExpr* maybeValue)
      : AstExpr(Kind, ExprType::Void),
        index_(index),
        default_(def),
        table_(Move(table)),
        value_(maybeValue)
    {}
    AstExpr& index() const { return index_; }
    AstRef& def() { return default_; }
    AstRefVector& table() { return table_; }
    AstExpr* maybeValue() { return value_; }
};

class AstFunc : public AstNode
{
    AstName name_;
    AstRef sig_;
    AstValTypeVector vars_;
    AstNameVector localNames_;
    AstExprVector body_;

  public:
    AstFunc(AstName name, AstRef sig, AstValTypeVector&& vars,
                AstNameVector&& locals, AstExprVector&& body)
      : name_(name),
        sig_(sig),
        vars_(Move(vars)),
        localNames_(Move(locals)),
        body_(Move(body))
    {}
    AstRef& sig() { return sig_; }
    const AstValTypeVector& vars() const { return vars_; }
    const AstNameVector& locals() const { return localNames_; }
    const AstExprVector& body() const { return body_; }
    AstName name() const { return name_; }
};

class AstGlobal : public AstNode
{
    AstName name_;
    bool isMutable_;
    ValType type_;
    Maybe<AstExpr*> init_;

  public:
    AstGlobal() : isMutable_(false), type_(ValType(TypeCode::Limit))
    {}

    explicit AstGlobal(AstName name, ValType type, bool isMutable,
                       Maybe<AstExpr*> init = Maybe<AstExpr*>())
      : name_(name), isMutable_(isMutable), type_(type), init_(init)
    {}

    AstName name() const { return name_; }
    bool isMutable() const { return isMutable_; }
    ValType type() const { return type_; }

    bool hasInit() const { return !!init_; }
    AstExpr& init() const { MOZ_ASSERT(hasInit()); return **init_; }
};

typedef AstVector<AstGlobal*> AstGlobalVector;

class AstImport : public AstNode
{
    AstName name_;
    AstName module_;
    AstName field_;
    DefinitionKind kind_;

    AstRef funcSig_;
    Limits limits_;
    AstGlobal global_;

  public:
    AstImport(AstName name, AstName module, AstName field, AstRef funcSig)
      : name_(name), module_(module), field_(field), kind_(DefinitionKind::Function), funcSig_(funcSig)
    {}
    AstImport(AstName name, AstName module, AstName field, DefinitionKind kind, Limits limits)
      : name_(name), module_(module), field_(field), kind_(kind), limits_(limits)
    {}
    AstImport(AstName name, AstName module, AstName field, AstGlobal global)
      : name_(name), module_(module), field_(field), kind_(DefinitionKind::Global), global_(global)
    {}

    AstName name() const { return name_; }
    AstName module() const { return module_; }
    AstName field() const { return field_; }

    DefinitionKind kind() const { return kind_; }
    AstRef& funcSig() {
        MOZ_ASSERT(kind_ == DefinitionKind::Function);
        return funcSig_;
    }
    Limits limits() const {
        MOZ_ASSERT(kind_ == DefinitionKind::Memory || kind_ == DefinitionKind::Table);
        return limits_;
    }
    const AstGlobal& global() const {
        MOZ_ASSERT(kind_ == DefinitionKind::Global);
        return global_;
    }
};

class AstExport : public AstNode
{
    AstName name_;
    DefinitionKind kind_;
    AstRef ref_;

  public:
    AstExport(AstName name, DefinitionKind kind, AstRef ref)
      : name_(name), kind_(kind), ref_(ref)
    {}
    explicit AstExport(AstName name, DefinitionKind kind)
      : name_(name), kind_(kind)
    {}
    AstName name() const { return name_; }
    DefinitionKind kind() const { return kind_; }
    AstRef& ref() { return ref_; }
};

class AstDataSegment : public AstNode
{
    AstExpr* offset_;
    AstNameVector fragments_;

  public:
    AstDataSegment(AstExpr* offset, AstNameVector&& fragments)
      : offset_(offset), fragments_(Move(fragments))
    {}

    AstExpr* offset() const { return offset_; }
    const AstNameVector& fragments() const { return fragments_; }
};

typedef AstVector<AstDataSegment*> AstDataSegmentVector;

class AstElemSegment : public AstNode
{
    AstExpr* offset_;
    AstRefVector elems_;

  public:
    AstElemSegment(AstExpr* offset, AstRefVector&& elems)
      : offset_(offset), elems_(Move(elems))
    {}

    AstExpr* offset() const { return offset_; }
    AstRefVector& elems() { return elems_; }
    const AstRefVector& elems() const { return elems_; }
};

typedef AstVector<AstElemSegment*> AstElemSegmentVector;

class AstStartFunc : public AstNode
{
    AstRef func_;

  public:
    explicit AstStartFunc(AstRef func)
      : func_(func)
    {}

    AstRef& func() {
        return func_;
    }
};

struct AstResizable
{
    AstName name;
    Limits limits;
    bool imported;

    AstResizable(Limits limits, bool imported, AstName name = AstName())
      : name(name),
        limits(limits),
        imported(imported)
    {}
};

class AstModule : public AstNode
{
  public:
    typedef AstVector<AstFunc*> FuncVector;
    typedef AstVector<AstImport*> ImportVector;
    typedef AstVector<AstExport*> ExportVector;
    typedef AstVector<AstSig*> SigVector;
    typedef AstVector<AstName> NameVector;
    typedef AstVector<AstResizable> AstResizableVector;

  private:
    typedef AstHashMap<AstSig*, uint32_t, AstSig> SigMap;

    LifoAlloc&           lifo_;
    SigVector            sigs_;
    SigMap               sigMap_;
    ImportVector         imports_;
    NameVector           funcImportNames_;
    AstResizableVector   tables_;
    AstResizableVector   memories_;
    ExportVector         exports_;
    Maybe<AstStartFunc>  startFunc_;
    FuncVector           funcs_;
    AstDataSegmentVector dataSegments_;
    AstElemSegmentVector elemSegments_;
    AstGlobalVector      globals_;

  public:
    explicit AstModule(LifoAlloc& lifo)
      : lifo_(lifo),
        sigs_(lifo),
        sigMap_(lifo),
        imports_(lifo),
        funcImportNames_(lifo),
        tables_(lifo),
        memories_(lifo),
        exports_(lifo),
        funcs_(lifo),
        dataSegments_(lifo),
        elemSegments_(lifo),
        globals_(lifo)
    {}
    bool init() {
        return sigMap_.init();
    }
    bool addMemory(AstName name, Limits memory) {
        return memories_.append(AstResizable(memory, false, name));
    }
    bool hasMemory() const {
        return !!memories_.length();
    }
    const AstResizableVector& memories() const {
        return memories_;
    }
    bool addTable(AstName name, Limits table) {
        return tables_.append(AstResizable(table, false, name));
    }
    bool hasTable() const {
        return !!tables_.length();
    }
    const AstResizableVector& tables() const {
        return tables_;
    }
    bool append(AstDataSegment* seg) {
        return dataSegments_.append(seg);
    }
    const AstDataSegmentVector& dataSegments() const {
        return dataSegments_;
    }
    bool append(AstElemSegment* seg) {
        return elemSegments_.append(seg);
    }
    const AstElemSegmentVector& elemSegments() const {
        return elemSegments_;
    }
    bool hasStartFunc() const {
        return !!startFunc_;
    }
    bool setStartFunc(AstStartFunc startFunc) {
        if (startFunc_)
            return false;
        startFunc_.emplace(startFunc);
        return true;
    }
    AstStartFunc& startFunc() {
        return *startFunc_;
    }
    bool declare(AstSig&& sig, uint32_t* sigIndex) {
        SigMap::AddPtr p = sigMap_.lookupForAdd(sig);
        if (p) {
            *sigIndex = p->value();
            return true;
        }
        *sigIndex = sigs_.length();
        auto* lifoSig = new (lifo_) AstSig(AstName(), Move(sig));
        return lifoSig &&
               sigs_.append(lifoSig) &&
               sigMap_.add(p, sigs_.back(), *sigIndex);
    }
    bool append(AstSig* sig) {
        uint32_t sigIndex = sigs_.length();
        if (!sigs_.append(sig))
            return false;
        SigMap::AddPtr p = sigMap_.lookupForAdd(*sig);
        return p || sigMap_.add(p, sig, sigIndex);
    }
    const SigVector& sigs() const {
        return sigs_;
    }
    bool append(AstFunc* func) {
        return funcs_.append(func);
    }
    const FuncVector& funcs() const {
        return funcs_;
    }
    bool append(AstImport* imp) {
        switch (imp->kind()) {
          case DefinitionKind::Function:
            if (!funcImportNames_.append(imp->name()))
                return false;
            break;
          case DefinitionKind::Table:
            if (!tables_.append(AstResizable(imp->limits(), true)))
                return false;
            break;
          case DefinitionKind::Memory:
            if (!memories_.append(AstResizable(imp->limits(), true)))
                return false;
            break;
          case DefinitionKind::Global:
            break;
        }

        return imports_.append(imp);
    }
    const ImportVector& imports() const {
        return imports_;
    }
    const NameVector& funcImportNames() const {
        return funcImportNames_;
    }
    size_t numFuncImports() const {
        return funcImportNames_.length();
    }
    bool append(AstExport* exp) {
        return exports_.append(exp);
    }
    const ExportVector& exports() const {
        return exports_;
    }
    bool append(AstGlobal* glob) {
        return globals_.append(glob);
    }
    const AstGlobalVector& globals() const {
        return globals_;
    }
};

class AstUnaryOperator final : public AstExpr
{
    Op op_;
    AstExpr* operand_;

  public:
    static const AstExprKind Kind = AstExprKind::UnaryOperator;
    explicit AstUnaryOperator(Op op, AstExpr* operand)
      : AstExpr(Kind, ExprType::Limit),
        op_(op), operand_(operand)
    {}

    Op op() const { return op_; }
    AstExpr* operand() const { return operand_; }
};

class AstBinaryOperator final : public AstExpr
{
    Op op_;
    AstExpr* lhs_;
    AstExpr* rhs_;

  public:
    static const AstExprKind Kind = AstExprKind::BinaryOperator;
    explicit AstBinaryOperator(Op op, AstExpr* lhs, AstExpr* rhs)
      : AstExpr(Kind, ExprType::Limit),
        op_(op), lhs_(lhs), rhs_(rhs)
    {}

    Op op() const { return op_; }
    AstExpr* lhs() const { return lhs_; }
    AstExpr* rhs() const { return rhs_; }
};

class AstTernaryOperator : public AstExpr
{
    Op op_;
    AstExpr* op0_;
    AstExpr* op1_;
    AstExpr* op2_;

  public:
    static const AstExprKind Kind = AstExprKind::TernaryOperator;
    AstTernaryOperator(Op op, AstExpr* op0, AstExpr* op1, AstExpr* op2)
      : AstExpr(Kind, ExprType::Limit),
        op_(op), op0_(op0), op1_(op1), op2_(op2)
    {}

    Op op() const { return op_; }
    AstExpr* op0() const { return op0_; }
    AstExpr* op1() const { return op1_; }
    AstExpr* op2() const { return op2_; }
};

class AstComparisonOperator final : public AstExpr
{
    Op op_;
    AstExpr* lhs_;
    AstExpr* rhs_;

  public:
    static const AstExprKind Kind = AstExprKind::ComparisonOperator;
    explicit AstComparisonOperator(Op op, AstExpr* lhs, AstExpr* rhs)
      : AstExpr(Kind, ExprType::Limit),
        op_(op), lhs_(lhs), rhs_(rhs)
    {}

    Op op() const { return op_; }
    AstExpr* lhs() const { return lhs_; }
    AstExpr* rhs() const { return rhs_; }
};

class AstConversionOperator final : public AstExpr
{
    Op op_;
    AstExpr* operand_;

  public:
    static const AstExprKind Kind = AstExprKind::ConversionOperator;
    explicit AstConversionOperator(Op op, AstExpr* operand)
      : AstExpr(Kind, ExprType::Limit),
        op_(op), operand_(operand)
    {}

    Op op() const { return op_; }
    AstExpr* operand() const { return operand_; }
};

// This is an artificial AST node which can fill operand slots in an AST
// constructed from parsing or decoding stack-machine code that doesn't have
// an inherent AST structure.
class AstPop final : public AstExpr
{
  public:
    static const AstExprKind Kind = AstExprKind::Pop;
    AstPop()
      : AstExpr(Kind, ExprType::Void)
    {}
};

// This is an artificial AST node which can be used to represent some forms
// of stack-machine code in an AST form. It is similar to Block, but returns the
// value of its first operand, rather than the last.
class AstFirst : public AstExpr
{
    AstExprVector exprs_;

  public:
    static const AstExprKind Kind = AstExprKind::First;
    explicit AstFirst(AstExprVector&& exprs)
      : AstExpr(Kind, ExprType::Limit),
        exprs_(Move(exprs))
    {}

    AstExprVector& exprs() { return exprs_; }
    const AstExprVector& exprs() const { return exprs_; }
};

} // end wasm namespace
} // end js namespace

#endif // namespace wasmast_h