// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

#ifndef RUNTIME_VM_COMPILER_FRONTEND_SCOPE_BUILDER_H_
#define RUNTIME_VM_COMPILER_FRONTEND_SCOPE_BUILDER_H_

#if defined(DART_PRECOMPILED_RUNTIME)
#error "AOT runtime should not use compiler sources (including header files)"
#endif  // defined(DART_PRECOMPILED_RUNTIME)

#include "vm/compiler/frontend/constant_reader.h"
#include "vm/compiler/frontend/kernel_translation_helper.h"
#include "vm/hash_map.h"
#include "vm/object.h"
#include "vm/parser.h"  // For ParsedFunction.

namespace dart {
namespace kernel {

class ScopeBuildingResult;

class ScopeBuilder {
 public:
  explicit ScopeBuilder(ParsedFunction* parsed_function);

  virtual ~ScopeBuilder() = default;

  ScopeBuildingResult* BuildScopes();

 private:
  void VisitField();

  void VisitProcedure();

  void VisitConstructor();

  void VisitFunctionNode();
  void VisitNode();
  void VisitInitializer();
  void VisitExpression();
  void VisitStatement();
  void VisitListOfExpressions();
  void VisitListOfNamedExpressions();
  void VisitArguments();
  void VisitVariableDeclaration();
  void VisitVariable();
  void VisitVariableGet(intptr_t declaration_binary_offset);
  void VisitDartType();
  void VisitInterfaceType(bool simple);
  void VisitFunctionType(bool simple);
  void VisitRecordType();
  void VisitTypeParameterType();
  void VisitIntersectionType();
  void VisitExtensionType();
  void VisitFutureOrType();
  void HandleLocalFunction(intptr_t parent_kernel_offset);

  AbstractType& BuildAndVisitVariableType();

  void EnterScope(intptr_t kernel_offset);
  void ExitScope(TokenPosition start_position, TokenPosition end_position);

  virtual void ReportUnexpectedTag(const char* variant, Tag tag);

  // This enum controls which parameters would be marked as requiring type
  // check on the callee side.
  enum ParameterTypeCheckMode {
    // All parameters will be checked.
    kTypeCheckAllParameters,

    // Only parameters marked as covariant or generic-covariant-impl will be
    // checked.
    kTypeCheckForNonDynamicallyInvokedMethod,

    // Only parameters *not* marked as covariant or generic-covariant-impl will
    // be checked. The rest would be checked in the method itself.
    // Inverse of kTypeCheckForNonDynamicallyInvokedMethod.
    kTypeCheckEverythingNotCheckedInNonDynamicallyInvokedMethod,

    // No parameters will be checked.
    kTypeCheckForStaticFunction,

    // No non-covariant checks are performed, and any covariant checks are
    // performed by the target.
    kTypeCheckForImplicitClosureFunction,
  };

  // This assumes that the reader is at a FunctionNode,
  // about to read the positional parameters.
  void AddPositionalAndNamedParameters(
      intptr_t pos,
      ParameterTypeCheckMode type_check_mode,
      const ProcedureAttributesMetadata& attrs);

  // This assumes that the reader is at a FunctionNode,
  // about to read a parameter (i.e. Variable).
  void AddParameter(intptr_t pos,
                    ParameterTypeCheckMode type_check_mode,
                    const ProcedureAttributesMetadata& attrs);

  LocalVariable* MakeVariable(
      TokenPosition declaration_pos,
      TokenPosition token_pos,
      const String& name,
      const AbstractType& type,
      intptr_t kernel_offset = LocalVariable::kNoKernelOffset,
      bool is_late = false,
      const InferredTypeMetadata* inferred_type_md = nullptr,
      const InferredTypeMetadata* inferred_arg_type_md = nullptr);

  void AddExceptionVariable(GrowableArray<LocalVariable*>* variables,
                            const char* prefix,
                            intptr_t nesting_depth);

  void FinalizeExceptionVariable(GrowableArray<LocalVariable*>* variables,
                                 GrowableArray<LocalVariable*>* raw_variables,
                                 const String& symbol,
                                 intptr_t nesting_depth);

  void AddTryVariables();
  void AddCatchVariables();
  void FinalizeCatchVariables();
  void AddSwitchVariable();

  // Record an assignment or reference to a variable.  If the occurrence is
  // in a nested function, ensure that the variable is handled properly as a
  // captured variable.
  LocalVariable* LookupVariable(intptr_t declaration_binary_offset);

  const String& GenerateName(const char* prefix, intptr_t suffix);

  void HandleLoadReceiver();
  void HandleSpecialLoad(LocalVariable** variable,
                         const String& symbol,
                         intptr_t kernel_offset);

  ScriptPtr Script() { return active_class_.ActiveScript(); }

  struct DepthState {
    explicit DepthState(intptr_t function)
        : loop_(0), function_(function), try_(0), catch_(0), finally_(0) {}

    intptr_t loop_;
    intptr_t function_;
    intptr_t try_;
    intptr_t catch_;
    intptr_t finally_;
  };

  ScopeBuildingResult* result_;
  ParsedFunction* parsed_function_;

  ActiveClass active_class_;

  TranslationHelper translation_helper_;
  Zone* zone_;

  LocalScope* current_function_scope_;
  LocalScope* scope_;
  DepthState depth_;

  bool needs_expr_temp_;
  TokenPosition first_body_token_position_ = TokenPosition::kNoSource;

  KernelReaderHelper helper_;
  ConstantReader constant_reader_;
  TypeTranslator type_translator_;
  InferredTypeMetadataHelper inferred_type_metadata_helper_;
  InferredTypeMetadataHelper inferred_arg_type_metadata_helper_;
  ProcedureAttributesMetadataHelper procedure_attributes_metadata_helper_;

  DISALLOW_COPY_AND_ASSIGN(ScopeBuilder);
};

struct FunctionScope {
  intptr_t kernel_offset;
  LocalScope* scope;
};

class ScopeBuildingResult : public ZoneObject {
 public:
  ScopeBuildingResult()
      : switch_variable(nullptr),
        finally_return_variable(nullptr),
        setter_value(nullptr),
        raw_variable_counter_(0) {}

  bool IsClosureWithEmptyContext(intptr_t function_node_offset) {
    for (intptr_t i = 0; i < closure_offsets_without_captures.length(); ++i) {
      if (closure_offsets_without_captures[i] == function_node_offset) {
        return true;
      }
    }
    return false;
  }

  IntMap<LocalVariable*> locals;
  IntMap<LocalScope*> scopes;
  GrowableArray<FunctionScope> function_scopes;

  // Non-nullptr when the function contains a switch statement.
  LocalVariable* switch_variable;

  // Non-nullptr when the function contains a return inside a finally block.
  LocalVariable* finally_return_variable;

  // Non-nullptr when the function is a setter.
  LocalVariable* setter_value;

  // Variables used in exception handlers, one per exception handler nesting
  // level.
  GrowableArray<LocalVariable*> exception_variables;
  GrowableArray<LocalVariable*> stack_trace_variables;
  GrowableArray<LocalVariable*> catch_context_variables;

  // These are used to access the raw exception/stacktrace variables (and are
  // used to put them into the captured variables in the context).
  GrowableArray<LocalVariable*> raw_exception_variables;
  GrowableArray<LocalVariable*> raw_stack_trace_variables;
  intptr_t raw_variable_counter_;

  // Remembers closure function kernel offsets that do not capture any
  // variables.
  GrowableArray<intptr_t> closure_offsets_without_captures;

  void PrintTo(BaseTextBuffer* f) const;
  const char* ToCString() const;

 private:
  DISALLOW_COPY_AND_ASSIGN(ScopeBuildingResult);
};

}  // namespace kernel
}  // namespace dart

#endif  // RUNTIME_VM_COMPILER_FRONTEND_SCOPE_BUILDER_H_
