text: // This file is distributed under the BSD License.
  // See "license.txt" for details.
  // Copyright 2009-2012, Jonathan Turner (jonathan@emptycrate.com)
  // Copyright 2009-2015, Jason Turner (jason@emptycrate.com)
  // http://www.chaiscript.com
  
  #ifndef CHAISCRIPT_COMMON_HPP_
  #define CHAISCRIPT_COMMON_HPP_
  
  #include <algorithm>
  #include <memory>
  #include <sstream>
  #include <stdexcept>
  #include <string>
  #include <vector>
  
  #include "../chaiscript_defines.hpp"
  #include "../dispatchkit/boxed_value.hpp"
  #include "../dispatchkit/dispatchkit.hpp"
  #include "../dispatchkit/proxy_functions.hpp"
  #include "../dispatchkit/type_info.hpp"
  
  namespace chaiscript {
  struct AST_Node;
  }  // namespace chaiscript
  
  namespace chaiscript
  {
  
    /// Signature of module entry point that all binary loadable modules must implement.
    typedef ModulePtr (*Create_Module_Func)();
  
  
    /// Types of AST nodes available to the parser and eval
    class AST_Node_Type {
    public:
      enum Type { Error, Int, Float, Id, Char, Str, Eol, Fun_Call, Inplace_Fun_Call, Arg_List, Variable, Equation, Var_Decl,
                  Comparison, Addition, Subtraction, Multiplication, Division, Modulus, Array_Call, Dot_Access, Quoted_String, Single_Quoted_String,
                  Lambda, Block, Def, While, If, For, Inline_Array, Inline_Map, Return, File, Prefix, Break, Continue, Map_Pair, Value_Range,
                  Inline_Range, Annotation, Try, Catch, Finally, Method, Attr_Decl, Shift, Equality, Bitwise_And, Bitwise_Xor, Bitwise_Or, 
                  Logical_And, Logical_Or, Reference, Switch, Case, Default, Ternary_Cond, Noop, Class, Binary, Arg, Global_Decl
      };
    };
  
    namespace
    {
  
      /// Helper lookup to get the name of each node type
      const char *ast_node_type_to_string(int ast_node_type) {
        const char *ast_node_types[] = { "Internal Parser Error", "Int", "Float", "Id", "Char", "Str", "Eol", "Fun_Call", "Inplace_Fun_Call", "Arg_List", "Variable", "Equation", "Var_Decl",
                                      "Comparison", "Addition", "Subtraction", "Multiplication", "Division", "Modulus", "Array_Call", "Dot_Access", "Quoted_String", "Single_Quoted_String",
                                      "Lambda", "Block", "Def", "While", "If", "For", "Inline_Array", "Inline_Map", "Return", "File", "Prefix", "Break", "Continue", "Map_Pair", "Value_Range",
                                      "Inline_Range", "Annotation", "Try", "Catch", "Finally", "Method", "Attr_Decl", "Shift", "Equality", "Bitwise_And", "Bitwise_Xor", "Bitwise_Or", 
                                      "Logical_And", "Logical_Or", "Reference", "Switch", "Case", "Default", "Ternary Condition", "Noop", "Class", "Binary", "Arg"};
  
        return ast_node_types[ast_node_type];
      }
    }
  
    /// \brief Convenience type for file positions
    struct File_Position {
      int line;
      int column;
  
      File_Position(int t_file_line, int t_file_column)
        : line(t_file_line), column(t_file_column) { }
  
      File_Position() : line(0), column(0) { }
    };
  
    struct Parse_Location {
      Parse_Location(std::string t_fname="", const int t_start_line=0, const int t_start_col=0,
          const int t_end_line=0, const int t_end_col=0)
        : start(t_start_line, t_start_col), 
          end(t_end_line, t_end_col),
          filename(std::make_shared<std::string>(std::move(t_fname)))
      {
      }
  
      Parse_Location(std::shared_ptr<std::string> t_fname, const int t_start_line=0, const int t_start_col=0,
          const int t_end_line=0, const int t_end_col=0)
        : start(t_start_line, t_start_col), 
          end(t_end_line, t_end_col),
          filename(std::move(t_fname))
      {
      }
  
  
  
      File_Position start;
      File_Position end;
      std::shared_ptr<std::string> filename;
    };
  
  
    /// \brief Typedef for pointers to AST_Node objects. Used in building of the AST_Node tree
    typedef std::shared_ptr<AST_Node> AST_NodePtr;
    typedef std::shared_ptr<const AST_Node> AST_NodePtr_Const;
  
  
    /// \brief Classes which may be thrown during error cases when ChaiScript is executing.
    namespace exception
    {
  
      /// Errors generated during parsing or evaluation
      struct eval_error : std::runtime_error {
        std::string reason;
        File_Position start_position;
        std::string filename;
        std::string detail;
        std::vector<AST_NodePtr_Const> call_stack;
  
        eval_error(const std::string &t_why, const File_Position &t_where, const std::string &t_fname,
            const std::vector<Boxed_Value> &t_parameters, const std::vector<chaiscript::Const_Proxy_Function> &t_functions,
            bool t_dot_notation,
            const chaiscript::detail::Dispatch_Engine &t_ss) CHAISCRIPT_NOEXCEPT :
          std::runtime_error(format(t_why, t_where, t_fname, t_parameters, t_dot_notation, t_ss)),
          reason(t_why), start_position(t_where), filename(t_fname), detail(format_detail(t_functions, t_dot_notation, t_ss)) 
        {}
  
        eval_error(const std::string &t_why, 
             const std::vector<Boxed_Value> &t_parameters, const std::vector<chaiscript::Const_Proxy_Function> &t_functions,
             bool t_dot_notation,
             const chaiscript::detail::Dispatch_Engine &t_ss) CHAISCRIPT_NOEXCEPT :
          std::runtime_error(format(t_why, t_parameters, t_dot_notation, t_ss)),
          reason(t_why), detail(format_detail(t_functions, t_dot_notation, t_ss))
        {}
  
  
        eval_error(const std::string &t_why, const File_Position &t_where, const std::string &t_fname) CHAISCRIPT_NOEXCEPT :
          std::runtime_error(format(t_why, t_where, t_fname)),
          reason(t_why), start_position(t_where), filename(t_fname)
        {}
  
        eval_error(const std::string &t_why) CHAISCRIPT_NOEXCEPT
          : std::runtime_error("Error: \"" + t_why + "\" "),
          reason(t_why) 
        {}
  
        eval_error(const eval_error &) = default;
  
        std::string pretty_print() const
        {
          std::ostringstream ss;
  
          ss << what();
          if (call_stack.size() > 0) {
            ss << "during evaluation at (" << fname(call_stack[0]) << " " << startpos(call_stack[0]) << ")\n";
            ss << '\n' << detail << '\n';
            ss << "  " << fname(call_stack[0]) << " (" << startpos(call_stack[0]) << ") '" << pretty(call_stack[0]) << "'";
            for (size_t j = 1; j < call_stack.size(); ++j) {
              if (id(call_stack[j]) != chaiscript::AST_Node_Type::Block
                  && id(call_stack[j]) != chaiscript::AST_Node_Type::File)
              {
                ss << '\n';
                ss << "  from " << fname(call_stack[j]) << " (" << startpos(call_stack[j]) << ") '" << pretty(call_stack[j]) << "'";
              }
            }
          }
          ss << '\n';
          return ss.str();
        }
  
        virtual ~eval_error() CHAISCRIPT_NOEXCEPT {}
  
      private:
  
        template<typename T>
          static int id(const T& t)
          {
            return t->identifier;
          }
  
        template<typename T>
          static std::string pretty(const T& t)
          {
            return t->pretty_print();
          }
  
        template<typename T>
          static const std::string &fname(const T& t)
          {
            return t->filename();
          }
  
        template<typename T>
          static std::string startpos(const T& t)
          {
            std::ostringstream oss;
            oss << t->start().line << ", " << t->start().column;
            return oss.str();
          }
  
        static std::string format_why(const std::string &t_why)
        {
          return "Error: \"" + t_why + "\"";
        }
  
        static std::string format_types(const Const_Proxy_Function &t_func,
            bool t_dot_notation,
            const chaiscript::detail::Dispatch_Engine &t_ss)
        {
          int arity = t_func->get_arity();
          std::vector<Type_Info> types = t_func->get_param_types();
  
          std::string retval;
          if (arity == -1)
          {
            retval = "(...)";
            if (t_dot_notation)
            {
              retval = "(Object)." + retval;
            }
          } else if (types.size() <= 1) {
            retval = "()";
          } else {
            std::stringstream ss;
            ss << "(";
  
            std::string paramstr;
  
            for (size_t index = 1;
                 index != types.size();
                 ++index)
            {
              paramstr += (types[index].is_const()?"const ":"");
              paramstr += t_ss.get_type_name(types[index]);
  
              if (index == 1 && t_dot_notation)
              {
                paramstr += ").(";
                if (types.size() == 2)
                {
                  paramstr += ", ";
                }
              } else {
                paramstr += ", ";
              }
            }
  
            ss << paramstr.substr(0, paramstr.size() - 2);
  
            ss << ")";
            retval = ss.str();
          }
  
  
          std::shared_ptr<const dispatch::Dynamic_Proxy_Function> dynfun 
            = std::dynamic_pointer_cast<const dispatch::Dynamic_Proxy_Function>(t_func);
  
          if (dynfun)
          {
            Proxy_Function f = dynfun->get_guard();
  
            if (f)
            {
              auto dynfunguard = std::dynamic_pointer_cast<const dispatch::Dynamic_Proxy_Function>(f);
              if (dynfunguard)
              {
                retval += " : " + format_guard(dynfunguard->get_parse_tree());
              }
            }
  
            retval += "\n          Defined at " + format_location(dynfun->get_parse_tree());        
          }
  
          return retval;
        }
  
        template<typename T>
          static std::string format_guard(const T &t)
          {
            return t->pretty_print();
          }
  
        template<typename T>
          static std::string format_location(const T &t)
          {
            std::ostringstream oss;
            oss << "(" << t->filename() << " " << t->start().line << ", " << t->start().column << ")"; 
  
            return oss.str();
  
          }
  
        static std::string format_detail(const std::vector<chaiscript::Const_Proxy_Function> &t_functions,
            bool t_dot_notation,
            const chaiscript::detail::Dispatch_Engine &t_ss)
        {
          std::stringstream ss;
          if (t_functions.size() == 1)
          {
            ss << "  Expected: " << format_types(t_functions[0], t_dot_notation, t_ss) << '\n';
          } else {
            ss << "  " << t_functions.size() << " overloads available:\n";
  
            for (const auto & t_function : t_functions)
            {
              ss << "      " << format_types((t_function), t_dot_notation, t_ss) << '\n';
            }
  
          }
  
          return ss.str();
  
        }
  
        static std::string format_parameters(const std::vector<Boxed_Value> &t_parameters,
            bool t_dot_notation,
            const chaiscript::detail::Dispatch_Engine &t_ss)
        {
          std::stringstream ss;
          ss << "(";
  
          if (!t_parameters.empty())
          {
            std::string paramstr;
  
            for (auto itr = t_parameters.begin();
                 itr != t_parameters.end();
                 ++itr)
            {
              paramstr += (itr->is_const()?"const ":"");
              paramstr += t_ss.type_name(*itr);
  
              if (itr == t_parameters.begin() && t_dot_notation)
              {
                paramstr += ").(";
                if (t_parameters.size() == 1)
                {
                  paramstr += ", ";
                }
              } else {
                paramstr += ", ";
              }
            }
  
            ss << paramstr.substr(0, paramstr.size() - 2);
          }
          ss << ")";
  
          return ss.str();
        }
  
        static std::string format_filename(const std::string &t_fname)
        {
          std::stringstream ss;
  
          if (t_fname != "__EVAL__")
          {
            ss << "in '" << t_fname << "' ";
          } else {
            ss << "during evaluation ";
          }
  
          return ss.str();
        }
  
        static std::string format_location(const File_Position &t_where)
        {
          std::stringstream ss;
          ss << "at (" << t_where.line << ", " << t_where.column << ")";
          return ss.str();
        }
  
        static std::string format(const std::string &t_why, const File_Position &t_where, const std::string &t_fname,
            const std::vector<Boxed_Value> &t_parameters, bool t_dot_notation, const chaiscript::detail::Dispatch_Engine &t_ss)
        {
          std::stringstream ss;
  
          ss << format_why(t_why);
          ss << " ";
  
          ss << "With parameters: " << format_parameters(t_parameters, t_dot_notation, t_ss);
          ss << " ";
  
          ss << format_filename(t_fname);
          ss << " ";
  
          ss << format_location(t_where);
  
          return ss.str();
        }
  
        static std::string format(const std::string &t_why, 
            const std::vector<Boxed_Value> &t_parameters, 
            bool t_dot_notation,
            const chaiscript::detail::Dispatch_Engine &t_ss)
        {
          std::stringstream ss;
  
          ss << format_why(t_why);
          ss << " ";
  
          ss << "With parameters: " << format_parameters(t_parameters, t_dot_notation, t_ss);
          ss << " ";
  
          return ss.str();
        }
  
        static std::string format(const std::string &t_why, const File_Position &t_where, const std::string &t_fname)
        {
          std::stringstream ss;
  
          ss << format_why(t_why);
          ss << " ";
  
          ss << format_filename(t_fname);
          ss << " ";
  
          ss << format_location(t_where);
  
          return ss.str();
        }
      };
  
  
      /// Errors generated when loading a file
      struct file_not_found_error : std::runtime_error {
        file_not_found_error(const std::string &t_filename) CHAISCRIPT_NOEXCEPT
          : std::runtime_error("File Not Found: " + t_filename)
        { }
  
        file_not_found_error(const file_not_found_error &) = default;
        virtual ~file_not_found_error() CHAISCRIPT_NOEXCEPT {}
      };
  
    }
  
   
    /// \brief Struct that doubles as both a parser ast_node and an AST node.
    struct AST_Node : std::enable_shared_from_this<AST_Node> {
      public:
        const int identifier; //< \todo shouldn't this be a strongly typed enum value?
        const std::string text;
        Parse_Location location;
        std::vector<AST_NodePtr> children;
        AST_NodePtr annotation;
  
        const std::string &filename() const {
          return *location.filename;
        }
  
        const File_Position &start() const {
          return location.start;
        }
  
        const File_Position &end() const {
          return location.end;
        }
  
        virtual std::string pretty_print() const
        {
          std::ostringstream oss;
  
          oss << text;
  
          for (auto & elem : this->children) {
            oss << elem->pretty_print();
          }
  
          return oss.str();
        }
  
  
        /// Prints the contents of an AST node, including its children, recursively
        std::string to_string(const std::string &t_prepend = "") const {
          std::ostringstream oss;
  
          oss << t_prepend << "(" << ast_node_type_to_string(this->identifier) << ") "
              << this->text << " : " << this->location.start.line << ", " << this->location.start.column << '\n';
  
          for (size_t j = 0; j < this->children.size(); ++j) {
            oss << this->children[j]->to_string(t_prepend + "  ");
          }
          return oss.str();
        }
  
        Boxed_Value eval(chaiscript::detail::Dispatch_Engine &t_e) const
        {
          try {
            return eval_internal(t_e);
          } catch (exception::eval_error &ee) {
            ee.call_stack.push_back(shared_from_this());
            throw;
          }
        }
  
        static bool get_bool_condition(const Boxed_Value &t_bv) {
          try {
            return boxed_cast<bool>(t_bv);
          }
          catch (const exception::bad_boxed_cast &) {
            throw exception::eval_error("Condition not boolean");
          }
        }
  
  
        void replace_child(const AST_NodePtr &t_child, const AST_NodePtr &t_new_child)
        {
          std::replace(children.begin(), children.end(), t_child, t_new_child);
        }
  
        virtual ~AST_Node() {}
  
      protected:
        AST_Node(std::string t_ast_node_text, int t_id, Parse_Location t_loc, 
                 std::vector<AST_NodePtr> t_children = std::vector<AST_NodePtr>()) :
          identifier(t_id), text(std::move(t_ast_node_text)),
          location(std::move(t_loc)),
          children(std::move(t_children))
        {
        }
  
        virtual Boxed_Value eval_internal(chaiscript::detail::Dispatch_Engine &) const
        {
          throw std::runtime_error("Undispatched ast_node (internal error)");
        }
  
      private:
        // Copy and assignment explicitly unimplemented
        AST_Node(const AST_Node &) = delete;
        AST_Node& operator=(const AST_Node &) = delete;
    };
  
  
    namespace eval
    {
      namespace detail
      {
        /// Special type for returned values
        struct Return_Value {
          Boxed_Value retval;
  
          Return_Value(Boxed_Value t_return_value) : retval(std::move(t_return_value)) { }
        };
  
  
        /// Special type indicating a call to 'break'
        struct Break_Loop {
          Break_Loop() { }
        };
  
  
        /// Special type indicating a call to 'continue'
        struct Continue_Loop {
          Continue_Loop() { }
        };
  
  
        /// Creates a new scope then pops it on destruction
        struct Scope_Push_Pop
        {
          Scope_Push_Pop(const Scope_Push_Pop &) = delete;
          Scope_Push_Pop& operator=(const Scope_Push_Pop &) = delete;
  
          Scope_Push_Pop(chaiscript::detail::Dispatch_Engine &t_de)
            : m_de(t_de)
          {
            m_de.new_scope();
          }
  
          ~Scope_Push_Pop()
          {
            m_de.pop_scope();
          }
  
  
          private:
  
          chaiscript::detail::Dispatch_Engine &m_de;
        };
  
        /// Creates a new function call and pops it on destruction
        struct Function_Push_Pop
        {
          Function_Push_Pop(const Function_Push_Pop &) = delete;
          Function_Push_Pop& operator=(const Function_Push_Pop &) = delete;
  
          Function_Push_Pop(chaiscript::detail::Dispatch_Engine &t_de)
            : m_de(t_de)
          {
            m_de.new_function_call();
          }
  
          ~Function_Push_Pop()
          {
            m_de.pop_function_call();
          }
  
          void save_params(const std::vector<Boxed_Value> &t_params)
          {
            m_de.save_function_params(t_params);
          }
  
          void save_params(std::initializer_list<Boxed_Value> t_params)
          {
            m_de.save_function_params(std::move(t_params));
          }
  
  
          private:
  
          chaiscript::detail::Dispatch_Engine &m_de;
        };
  
        /// Creates a new scope then pops it on destruction
        struct Stack_Push_Pop
        {
          Stack_Push_Pop(const Stack_Push_Pop &) = delete;
          Stack_Push_Pop& operator=(const Stack_Push_Pop &) = delete;
  
          Stack_Push_Pop(chaiscript::detail::Dispatch_Engine &t_de)
            : m_de(t_de)
          {
            m_de.new_stack();
          }
  
          ~Stack_Push_Pop()
          {
            m_de.pop_stack();
          }
  
  
          private:
  
          chaiscript::detail::Dispatch_Engine &m_de;
        };
      }
    }
  }
  
  #endif /* _CHAISCRIPT_COMMON_HPP */
  
  