/* vim: set ts=4 sw=4 tw=99 et:
 *
 * Copyright (C) 2012 David Anderson
 *
 * This file is part of JITCraft.
 *
 * JITCraft is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 * 
 * Foobar is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * JITCraft. If not, see http://www.gnu.org/licenses/.
 */
#ifndef _include_semantic_analysis_h_
#define _include_semantic_analysis_h_

#include <assert.h>
#include "AST.h"

namespace ke {

class SemanticAnalysis : public AstVisitor
{
    Zone *zone_;
    CompileContext &cc_;
    ParseTree *tree_;
    Scope *scope_;
    ScopedRoot<Type> function_;

    enum ReturnStatus
    {
        Returns_Never,
        Returns_Sometimes,
        Returns_Always
    };

    // Tuple that describes whether a path terminates control flow, and
    // if it is guaranteed to end in a return.
    class ControlStatus
    {
        bool terminates_;
        ReturnStatus returns_;

      public:
        ControlStatus(bool terminates, ReturnStatus returns)
          : terminates_(terminates),
            returns_(returns)
        {
        }

        bool terminates() const {
            return terminates_;
        }
        ReturnStatus returns() const {
            return returns_;
        }
    };

    // Used to keep track of terminating control flow and return statements
    // inside control structures.
    class ControlScope
    {
        ReturnStatus returns_;
        ControlScope **prevp_;
        ControlScope *prev_;
        bool terminates_;

      public:
        ControlScope(ControlScope **prev)
          : returns_(Returns_Never),
            prevp_(prev),
            prev_(*prev),
            terminates_(false)
        {
            *prevp_ = this;
        }

        ControlStatus status() const {
            return ControlStatus(terminates_, returns_);
        }

        // We allow multiple terminators, but only the first one takes effect.
        // For example, |break; continue;| is legal but the continue is
        // unreachable.
        void returns(ReturnStatus status) {
            if (terminates_)
                return;
            if (status > returns_)
                returns_ = status;
        }
        void terminate(ReturnStatus status) {
            if (terminates_)
                return;
            terminates_ = true;
            returns_ = status;
        }
        void merge(const ControlStatus &left, const ControlStatus &right);

        ~ControlScope()
        {
            *prevp_ = prev_;
        }
    };

    class LoopScope
    {
        LoopScope **prevp_;
        LoopScope *prev_;
        unsigned breaks_;
        unsigned continues_;

      public:
        LoopScope(LoopScope **prev)
          : prevp_(prev),
            prev_(*prev),
            breaks_(0),
            continues_(0)
        {
            *prevp_ = this;
        }

        ~LoopScope()
        {
            *prevp_ = prev_;
        }

        void addBreak() {
            breaks_++;
        }
        void addContinue() {
            continues_++;
        }
        unsigned breaks() const {
            return breaks_;
        }
        unsigned continues() const {
            return continues_;
        }
    };

    ControlScope *control_;
    LoopScope *loop_;

  private:
    bool canTest(AstNode *node, Handle<Type> type);
    void unaryTypeError(AstNode *node, TokenKind token, Handle<Type> type);
    void binaryTypeError(AstNode *node, TokenKind token, Handle<Type> left, Handle<Type> right);
    Type *typeCheckBinary(AstNode *node, TokenKind token, Handle<Type> left, Handle<Type> right);

    // These check if one type is compatible with another type, for various
    // purposes, like:
    //      return (expr) matches function return type
    //      call(a, b)    matches expected arg type
    //      a = b         matches declared type
    //
    bool typeCheckArrayAssignment(AstNode *node, Handle<Type> given, Handle<Type> actual, bool forAssign);
    bool typeCheckAssignment(AstNode *node, Handle<Type> given, Handle<Type> actual, bool forAssign);
    bool typeCheckParameter(AstNode *node, Handle<Type> given, Handle<Type> actual);

    Type *visitForLoad(Expression *expression);
    bool preanalyze(FunctionStatement *node);
    Type *visitForLValue(Expression *expression);
    Type *visitAndApplyType(Expression *expression);
    ControlStatus visitForControl(Statement *statement);

    void markCallResultUsed(CallExpression *call);

  public:
    SemanticAnalysis(Zone *zone, CompileContext &cc, ParseTree *tree);
    bool analyze();

  public:
    void visit(NameProxy *name);
    void visit(Assignment *node);
    void visit(BinaryExpression *node);
    void visit(ReturnStatement *node);
    void visit(ForStatement *node);
    void visit(BlockStatement *node);
    void visit(VariableDeclaration *node);
    void visit(IntegerLiteral *node);
    void visit(ExpressionStatement *node);
    void visit(FunctionStatement *node);
    void visit(CallExpression *node);
    void visit(IfStatement *node);
    void visit(IndexExpression *node);
    void visit(EnumStatement *node);
    void visit(FloatLiteral *node);
    void visit(WhileStatement *node);
    void visit(BreakStatement *node);
    void visit(ContinueStatement *node);
    void visit(StringLiteral *node);
    void visit(IncDecExpression *node);
    void visit(UnaryExpression *node);
    void visit(TernaryExpression *node);
};

} // namespace ke

#endif // _include_semantic_analysis_h_
