/* m2linemap.cc provides an interface to GCC linemaps.
Copyright (C) 2012-2023 Free Software Foundation, Inc.
Contributed by Gaius Mulley <gaius@glam.ac.uk>.
This file is part of GNU Modula-2.
GNU Modula-2 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, or (at your option)
any later version.
GNU Modula-2 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 GNU Modula-2; see the file COPYING3.  If not see
<http://www.gnu.org/licenses/>.  */
#include "gcc-consolidation.h"
/* Utilize some of the C build routines */
#include "../gm2-lang.h"
#include "../m2-tree.h"
#include "m2assert.h"
#include "m2block.h"
#include "m2decl.h"
#include "m2expr.h"
#include "m2options.h"
#include "m2tree.h"
#include "m2type.h"
#define m2linemap_c
#include "m2linemap.h"
#include "m2color.h"
#include <string>
static int inFile = FALSE;
#if defined(__cplusplus)
#define EXTERN extern "C"
#else
#define EXTERN
#endif
/* Start getting locations from a new file.  */
EXTERN
void
m2linemap_StartFile (void *filename, unsigned int linebegin)
{
  if (inFile)
    m2linemap_EndFile ();
  linemap_add (line_table, LC_ENTER, false,
               xstrdup (reinterpret_cast<char *> (filename)), linebegin);
  inFile = TRUE;
}
/* Tell the line table the file has ended.  */
EXTERN
void
m2linemap_EndFile (void)
{
  linemap_add (line_table, LC_LEAVE, 0, NULL, 0);
  inFile = FALSE;
}
/* Indicate that there is a new source file line number with a
   maximum width.  */
EXTERN
void
m2linemap_StartLine (unsigned int linenumber, unsigned int linesize)
{
  linemap_line_start (line_table, linenumber, linesize);
}
/* GetLocationColumn, returns a location_t based on the current line
   number and column.  */
EXTERN
location_t
m2linemap_GetLocationColumn (unsigned int column)
{
  return linemap_position_for_column (line_table, column);
}
/* GetLocationRange, returns a location based on the start column
   and end column.  */
EXTERN
location_t
m2linemap_GetLocationRange (unsigned int start, unsigned int end)
{
  location_t caret = m2linemap_GetLocationColumn (start);
  source_range where;
  where.m_start = linemap_position_for_column (line_table, start);
  where.m_finish = linemap_position_for_column (line_table, end);
  return make_location (caret, where);
}
static
int
isSrcLocation (location_t location)
{
  return (location != BUILTINS_LOCATION) && (location != UNKNOWN_LOCATION);
}
/* GetLocationBinary, returns a location based on the expression
   start caret finish locations.  */
EXTERN
location_t
m2linemap_GetLocationBinary (location_t caret, location_t start, location_t finish)
{
  if (isSrcLocation (start) && isSrcLocation (finish) && isSrcLocation (caret)
    && (m2linemap_GetFilenameFromLocation (start) != NULL))
    {
      linemap_add (line_table, LC_ENTER, false, xstrdup (m2linemap_GetFilenameFromLocation (start)), 1);
      gcc_assert (inFile);
      location_t location = make_location (caret, start, finish);
      return location;
    }
  return caret;
}
/* GetLineNoFromLocation - returns the lineno given a location.  */
EXTERN
int
m2linemap_GetLineNoFromLocation (location_t location)
{
  if (isSrcLocation (location) && (!M2Options_GetCpp ()))
    {
      expanded_location xl = expand_location (location);
      return xl.line;
    }
  return 0;
}
/* GetColumnNoFromLocation - returns the columnno given a location.  */
EXTERN
int
m2linemap_GetColumnNoFromLocation (location_t location)
{
  if (isSrcLocation (location) && (!M2Options_GetCpp ()))
    {
      expanded_location xl = expand_location (location);
      return xl.column;
    }
  return 0;
}
/* GetFilenameFromLocation - returns the filename given a location.  */
EXTERN
const char *
m2linemap_GetFilenameFromLocation (location_t location)
{
  if (isSrcLocation (location) && (!M2Options_GetCpp ()))
    {
      expanded_location xl = expand_location (location);
      return xl.file;
    }
  return NULL;
}
/* ErrorAt - issue an error message.  */
EXTERN
void
m2linemap_ErrorAt (location_t location, char *message)
{
  error_at (location, "%s", message);
}
/* m2linemap_ErrorAtf - wraps up an error message.  */
static void
m2linemap_ErrorAtf_1 (location_t location, const char *message, ...)
{
  diagnostic_info diagnostic;
  va_list ap;
  rich_location richloc (line_table, location);
  va_start (ap, message);
  diagnostic_set_info (&diagnostic, message, &ap, &richloc, DK_ERROR);
  diagnostic_report_diagnostic (global_dc, &diagnostic);
  va_end (ap);
}
void
m2linemap_ErrorAtf (location_t location, const char *message)
{
  m2linemap_ErrorAtf_1 (location, "%s", message);
}
/* m2linemap_WarningAtf - wraps up a warning message.  */
static void
m2linemap_WarningAtf_1 (location_t location, const char *message, ...)
{
  diagnostic_info diagnostic;
  va_list ap;
  rich_location richloc (line_table, location);
  va_start (ap, message);
  diagnostic_set_info (&diagnostic, message, &ap, &richloc, DK_WARNING);
  diagnostic_report_diagnostic (global_dc, &diagnostic);
  va_end (ap);
}
void
m2linemap_WarningAtf (location_t location, const char *message)
{
  m2linemap_WarningAtf_1 (location, "%s", message);
}
/* m2linemap_NoteAtf - wraps up a note message.  */
static void
m2linemap_NoteAtf_1 (location_t location, const char *message, ...)
{
  diagnostic_info diagnostic;
  va_list ap;
  rich_location richloc (line_table, location);
  va_start (ap, message);
  diagnostic_set_info (&diagnostic, message, &ap, &richloc, DK_NOTE);
  diagnostic_report_diagnostic (global_dc, &diagnostic);
  va_end (ap);
}
void
m2linemap_NoteAtf (location_t location, const char *message)
{
  m2linemap_NoteAtf_1 (location, "%s", message);
}
/* m2linemap_internal_error - allow Modula-2 to use the GCC internal error.  */
void
m2linemap_internal_error (const char *message)
{
  internal_error ("%s", message);
}
/* Code derived from rust.  */
static std::string
mformat_value ()
{
  return std::string (xstrerror (errno));
}
static std::string
expand_format (const char *fmt)
{
  std::string result;
  for (const char *c = fmt; *c; ++c)
    {
      if (*c != '%')
	{
	  result += *c;
	  continue;
	}
      c++;
      switch (*c)
	{
	  case '\0': {
	    // malformed format string
	    gcc_unreachable ();
	  }
	  case '%': {
	    result += '%';
	    break;
	  }
	  case 'm': {
	    result += mformat_value ();
	    break;
	  }
	  case '<': {
	    result += m2color_open_quote ();
	    break;
	  }
	  case '>': {
	    result += m2color_close_quote ();
	    break;
	  }
	  case 'q': {
	    result += m2color_open_quote ();
	    c++;
	    if (*c == 'm')
	      result += mformat_value ();
	    else
	      {
		result += '%';
		result += *c;
	      }
	    result += m2color_close_quote ();
	    break;
	  }
	  default: {
	    result += '%';
	    result += *c;
	  }
	}
    }
  return result;
}
static std::string
expand_message (const char *fmt, va_list ap)
{
  char *mbuf = 0;
  std::string expanded_fmt = expand_format (fmt);
  int nwr = vasprintf (&mbuf, expanded_fmt.c_str (), ap);
  if (nwr == -1)
    {
      // memory allocation failed
      error_at (UNKNOWN_LOCATION,
		"memory allocation failed in vasprintf");
      gcc_assert (0);
    }
  std::string rval = std::string (mbuf);
  free (mbuf);
  return rval;
}
static void
gm2_internal_error_at (location_t location, const std::string &errmsg)
{
  expanded_location exp_loc = expand_location (location);
  std::string loc_str;
  std::string file_str;
  if (exp_loc.file == NULL)
    file_str.clear ();
  else
    file_str = std::string (exp_loc.file);
  if (! file_str.empty ())
    {
      loc_str += file_str;
      loc_str += ':';
      loc_str += std::to_string (exp_loc.line);
      loc_str += ':';
      loc_str += std::to_string (exp_loc.column);
    }
  if (loc_str.empty ())
    internal_error ("%s", errmsg.c_str ());
  else
    internal_error ("at %s, %s", loc_str.c_str (), errmsg.c_str ());
}
void
m2linemap_internal_error_at (location_t location, const char *fmt, ...)
{
  va_list ap;
  va_start (ap, fmt);
  gm2_internal_error_at (location, expand_message (fmt, ap));
  va_end (ap);
}
/* UnknownLocation - return the predefined location representing an
   unknown location.  */
EXTERN
location_t
m2linemap_UnknownLocation (void)
{
  return UNKNOWN_LOCATION;
}
/* BuiltinsLocation - return the predefined location representing a
   builtin.  */
EXTERN
location_t
m2linemap_BuiltinsLocation (void)
{
  return BUILTINS_LOCATION;
}