/* TID parsing for GDB, the GNU debugger.

   Copyright (C) 2015-2017 Free Software Foundation, Inc.

   This file is part of GDB.

   This program 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.

   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.  */

#include "defs.h"
#include "tid-parse.h"
#include "inferior.h"
#include "gdbthread.h"
#include <ctype.h>

/* See tid-parse.h.  */

void ATTRIBUTE_NORETURN
invalid_thread_id_error (const char *string)
{
  error (_("Invalid thread ID: %s"), string);
}

/* Wrapper for get_number_trailer that throws an error if we get back
   a negative number.  We'll see a negative value if the number is
   stored in a negative convenience variable (e.g., $minus_one = -1).
   STRING is the parser string to be used in the error message if we
   do get back a negative number.  */

static int
get_positive_number_trailer (const char **pp, int trailer, const char *string)
{
  int num;

  num = get_number_trailer (pp, trailer);
  if (num < 0)
    error (_("negative value: %s"), string);
  return num;
}

/* See tid-parse.h.  */

struct thread_info *
parse_thread_id (const char *tidstr, const char **end)
{
  const char *number = tidstr;
  const char *dot, *p1;
  struct thread_info *tp;
  struct inferior *inf;
  int thr_num;
  int explicit_inf_id = 0;

  dot = strchr (number, '.');

  if (dot != NULL)
    {
      /* Parse number to the left of the dot.  */
      int inf_num;

      p1 = number;
      inf_num = get_positive_number_trailer (&p1, '.', number);
      if (inf_num == 0)
	invalid_thread_id_error (number);

      inf = find_inferior_id (inf_num);
      if (inf == NULL)
	error (_("No inferior number '%d'"), inf_num);

      explicit_inf_id = 1;
      p1 = dot + 1;
    }
  else
    {
      inf = current_inferior ();

      p1 = number;
    }

  thr_num = get_positive_number_trailer (&p1, 0, number);
  if (thr_num == 0)
    invalid_thread_id_error (number);

  ALL_THREADS (tp)
    {
      if (ptid_get_pid (tp->ptid) == inf->pid
	  && tp->per_inf_num == thr_num)
	break;
    }

  if (tp == NULL)
    {
      if (show_inferior_qualified_tids () || explicit_inf_id)
	error (_("Unknown thread %d.%d."), inf->num, thr_num);
      else
	error (_("Unknown thread %d."), thr_num);
    }

  if (end != NULL)
    *end = p1;

  return tp;
}

/* See tid-parse.h.  */

void
tid_range_parser_init (struct tid_range_parser *parser, const char *tidlist,
		       int default_inferior)
{
  parser->state = TID_RANGE_STATE_INFERIOR;
  parser->string = tidlist;
  parser->inf_num = 0;
  parser->qualified = 0;
  parser->default_inferior = default_inferior;
}

/* See tid-parse.h.  */

int
tid_range_parser_finished (struct tid_range_parser *parser)
{
  switch (parser->state)
    {
    case TID_RANGE_STATE_INFERIOR:
      return *parser->string == '\0';
    case TID_RANGE_STATE_THREAD_RANGE:
    case TID_RANGE_STATE_STAR_RANGE:
      return parser->range_parser.finished;
    }

  gdb_assert_not_reached (_("unhandled state"));
}

/* See tid-parse.h.  */

const char *
tid_range_parser_string (struct tid_range_parser *parser)
{
  switch (parser->state)
    {
    case TID_RANGE_STATE_INFERIOR:
      return parser->string;
    case TID_RANGE_STATE_THREAD_RANGE:
    case TID_RANGE_STATE_STAR_RANGE:
      return parser->range_parser.string;
    }

  gdb_assert_not_reached (_("unhandled state"));
}

/* See tid-parse.h.  */

void
tid_range_parser_skip (struct tid_range_parser *parser)
{
  gdb_assert ((parser->state == TID_RANGE_STATE_THREAD_RANGE
	       || parser->state == TID_RANGE_STATE_STAR_RANGE)
	      && parser->range_parser.in_range);

  tid_range_parser_init (parser, parser->range_parser.end_ptr,
			 parser->default_inferior);
}

/* See tid-parse.h.  */

int
tid_range_parser_qualified (struct tid_range_parser *parser)
{
  return parser->qualified;
}

/* Helper for tid_range_parser_get_tid and
   tid_range_parser_get_tid_range.  Return the next range if THR_END
   is non-NULL, return a single thread ID otherwise.  */

static int
get_tid_or_range (struct tid_range_parser *parser, int *inf_num,
		  int *thr_start, int *thr_end)
{
  if (parser->state == TID_RANGE_STATE_INFERIOR)
    {
      const char *p;
      const char *space;

      space = skip_to_space (parser->string);

      p = parser->string;
      while (p < space && *p != '.')
	p++;
      if (p < space)
	{
	  const char *dot = p;

	  /* Parse number to the left of the dot.  */
	  p = parser->string;
	  parser->inf_num
	    = get_positive_number_trailer (&p, '.', parser->string);
	  if (parser->inf_num == 0)
	    return 0;

	  parser->qualified = 1;
	  p = dot + 1;

	  if (isspace (*p))
	    return 0;
	}
      else
	{
	  parser->inf_num = parser->default_inferior;
	  parser->qualified = 0;
	  p = parser->string;
	}

      init_number_or_range (&parser->range_parser, p);
      if (p[0] == '*' && (p[1] == '\0' || isspace (p[1])))
	{
	  /* Setup the number range parser to return numbers in the
	     whole [1,INT_MAX] range.  */
	  number_range_setup_range (&parser->range_parser, 1, INT_MAX,
				    skip_spaces_const (p + 1));
	  parser->state = TID_RANGE_STATE_STAR_RANGE;
	}
      else
	parser->state = TID_RANGE_STATE_THREAD_RANGE;
    }

  *inf_num = parser->inf_num;
  *thr_start = get_number_or_range (&parser->range_parser);
  if (*thr_start < 0)
    error (_("negative value: %s"), parser->string);
  if (*thr_start == 0)
    {
      parser->state = TID_RANGE_STATE_INFERIOR;
      return 0;
    }

  /* If we successfully parsed a thread number or finished parsing a
     thread range, switch back to assuming the next TID is
     inferior-qualified.  */
  if (parser->range_parser.end_ptr == NULL
      || parser->range_parser.string == parser->range_parser.end_ptr)
    {
      parser->state = TID_RANGE_STATE_INFERIOR;
      parser->string = parser->range_parser.string;

      if (thr_end != NULL)
	*thr_end = *thr_start;
    }

  /* If we're midway through a range, and the caller wants the end
     value, return it and skip to the end of the range.  */
  if (thr_end != NULL
      && (parser->state == TID_RANGE_STATE_THREAD_RANGE
	  || parser->state == TID_RANGE_STATE_STAR_RANGE))
    {
      *thr_end = parser->range_parser.end_value;
      tid_range_parser_skip (parser);
    }

  return (*inf_num != 0 && *thr_start != 0);
}

/* See tid-parse.h.  */

int
tid_range_parser_get_tid_range (struct tid_range_parser *parser, int *inf_num,
				int *thr_start, int *thr_end)
{
  gdb_assert (inf_num != NULL && thr_start != NULL && thr_end != NULL);

  return get_tid_or_range (parser, inf_num, thr_start, thr_end);
}

/* See tid-parse.h.  */

int
tid_range_parser_get_tid (struct tid_range_parser *parser,
			  int *inf_num, int *thr_num)
{
  gdb_assert (inf_num != NULL && thr_num != NULL);

  return get_tid_or_range (parser, inf_num, thr_num, NULL);
}

/* See tid-parse.h.  */

int
tid_range_parser_star_range (struct tid_range_parser *parser)
{
  return parser->state == TID_RANGE_STATE_STAR_RANGE;
}

/* See gdbthread.h.  */

int
tid_is_in_list (const char *list, int default_inferior,
		int inf_num, int thr_num)
{
  struct tid_range_parser parser;

  if (list == NULL || *list == '\0')
    return 1;

  tid_range_parser_init (&parser, list, default_inferior);
  while (!tid_range_parser_finished (&parser))
    {
      int tmp_inf, tmp_thr_start, tmp_thr_end;

      if (!tid_range_parser_get_tid_range (&parser, &tmp_inf,
					   &tmp_thr_start, &tmp_thr_end))
	invalid_thread_id_error (parser.string);
      if (tmp_inf == inf_num
	  && tmp_thr_start <= thr_num && thr_num <= tmp_thr_end)
	return 1;
    }
  return 0;
}
