/*****************************************************************************\
*                                                                             *
*  Name   : cstrip                                                            *
*  Author : Chris Koeritz                                                     *
*                                                                             *
*  Purpose:                                                                   *
*                                                                             *
*    Strips out C and C++ comments from a file.                               *
*                                                                             *
*******************************************************************************
* Copyright (c) 1993-$now By Author.  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 2 of   *
* the License or (at your option) any later version.  This is online at:      *
*     http://www.fsf.org/copyleft/gpl.html                                    *
* Please send any updates to: fred@gruntose.com                               *
\*****************************************************************************/

#include <basis/definitions.h>
#include <basis/guards.h>
#include <basis/istring.h>
#include <basis/log_base.h>
#include <mechanisms/state_machine.h>
#include <opsystem/application_shell.h>
#include <opsystem/byte_filer.h>
#include <loggers/file_logger.h>
#include <data_struct/static_memory_gremlin.h>

#include <stdio.h>

HOOPLE_STARTUP_CODE;

// forward.
class cstrip;

#define SLASH    '/'
#define ASTERISK '*'
#define LAST     '\177'
#define NEWLINE  '\n'

int make_128(int c)
{
  if (feof(stdin)) return NEWLINE;
  else return (c > LAST? c - LAST : c);
}

////////////////////////////////////////////////////////////////////////////

class common_dude : public state_machine
{
public:
  int _carriage_returns;
  int _spaces;
  cstrip &_parent;

  common_dude(cstrip &parent)
  : state_machine(),
    _carriage_returns(0),
    _spaces(0),
    _parent(parent)
  {}

  virtual int update();
    // handles pulses on the state machine.
};

////////////////////////////////////////////////////////////////////////////

class cstrip : public application_shell
{
public:
  transition_map stripper;

  enum strip_states {
    PRINTING,
    GOT_ONE_SLASH,
    GOT_TWO_SLASHES,
    CONTINUED_COMMENT,
    GOT_SLASH_ASTERISK,
    GOT_SECOND_ASTERISK
  };

  cstrip() : application_shell("cstrip"), stripper() {
    program_wide_logger().eol(log_base::NO_ENDING);  // set for our print-outs.
  }

  IMPLEMENT_CLASS_NAME("cstrip");

  int execute();

  void setup_map(transition_map &stripper);

  int updater(common_dude &machine);
    // handles pulses by routing to specific state handlers below.

  // handlers for certain states.
  int bogus_function(common_dude &machine);
  int got_two_slashes(common_dude &machine);
  int printing(common_dude &machine);
  int one_slash(common_dude &machine);
};

////////////////////////////////////////////////////////////////////////////

int common_dude::update() { return _parent.updater(*this); }

////////////////////////////////////////////////////////////////////////////

int cstrip::execute()
{
  setup_map(stripper);
  common_dude zoot(*this);
  if (!stripper.reset(zoot))
    non_continuable_error(class_name(), "execute", "map is not valid!");
  while (!feof(stdin)) stripper.pulse(zoot, make_128(fgetc(stdin)));
  return 0;
}

void cstrip::setup_map(transition_map &stripper)
{
  stripper.add_state(PRINTING);
  stripper.add_range_transition(PRINTING, GOT_ONE_SLASH, SLASH, SLASH);
  stripper.add_simple_transition(PRINTING, PRINTING);

  stripper.add_state(GOT_ONE_SLASH);
  stripper.add_range_transition(GOT_ONE_SLASH, GOT_TWO_SLASHES, SLASH, SLASH);
  stripper.add_range_transition(GOT_ONE_SLASH, GOT_SLASH_ASTERISK, ASTERISK,
      ASTERISK);
  stripper.add_simple_transition(GOT_ONE_SLASH, PRINTING);

  stripper.add_state(CONTINUED_COMMENT);
  stripper.add_range_transition(CONTINUED_COMMENT, GOT_TWO_SLASHES, '\0',
      NEWLINE-1);
  stripper.add_range_transition(CONTINUED_COMMENT, GOT_TWO_SLASHES, NEWLINE,
      NEWLINE);
  stripper.add_range_transition(CONTINUED_COMMENT, GOT_TWO_SLASHES, NEWLINE+1,
      LAST);

  stripper.add_state(GOT_TWO_SLASHES);
  stripper.add_range_transition(GOT_TWO_SLASHES, GOT_TWO_SLASHES, '\0',
      NEWLINE-1);
  stripper.add_simple_transition(GOT_TWO_SLASHES, PRINTING);
  stripper.add_range_transition(GOT_TWO_SLASHES, GOT_TWO_SLASHES, NEWLINE+1,
      '\\'-1);
  stripper.add_range_transition(GOT_TWO_SLASHES, CONTINUED_COMMENT, '\\',
      '\\');
  stripper.add_range_transition(GOT_TWO_SLASHES, GOT_TWO_SLASHES, '\\'+1,
      LAST);

  stripper.add_state(GOT_SLASH_ASTERISK);
  stripper.add_range_transition(GOT_SLASH_ASTERISK, GOT_SLASH_ASTERISK, '\0',
      ASTERISK-1);
  stripper.add_range_transition(GOT_SLASH_ASTERISK, GOT_SECOND_ASTERISK,
      ASTERISK, ASTERISK);
  stripper.add_range_transition(GOT_SLASH_ASTERISK, GOT_SLASH_ASTERISK,
      ASTERISK+1, LAST);

  stripper.add_state(GOT_SECOND_ASTERISK);
  stripper.add_range_transition(GOT_SECOND_ASTERISK, GOT_SLASH_ASTERISK, 0,
      ASTERISK-1);
  stripper.add_range_transition(GOT_SECOND_ASTERISK, GOT_SECOND_ASTERISK,
      ASTERISK, ASTERISK);
  stripper.add_range_transition(GOT_SECOND_ASTERISK, GOT_SECOND_ASTERISK,
      ASTERISK+1, SLASH-1);
  stripper.add_simple_transition(GOT_SECOND_ASTERISK, PRINTING);
  stripper.add_range_transition(GOT_SECOND_ASTERISK, GOT_SLASH_ASTERISK,
      SLASH+1, LAST);
}

int cstrip::updater(common_dude &machine)
{
  switch (machine.current()) {
    case PRINTING: return printing(machine);
    case GOT_ONE_SLASH: return one_slash(machine);
    case GOT_TWO_SLASHES: return got_two_slashes(machine);
    case GOT_SLASH_ASTERISK: return bogus_function(machine);
    case GOT_SECOND_ASTERISK: return got_two_slashes(machine);

    case CONTINUED_COMMENT: // intentional fall-through.
    default:
      return bogus_function(machine);
  }
}

int cstrip::bogus_function(common_dude &machine)
{
  istring to_print(istring::SPRINTF, "state %d should not reach here.  "
      "trigger is %c", machine.current(), machine.trigger());
  non_continuable_error(class_name(), "bogus_function", to_print.s());
  return 0;
}

int cstrip::got_two_slashes(common_dude &machine)
{
  machine._carriage_returns++;
  machine._spaces = 0;
  if (machine._carriage_returns <= 2)
    machine._parent.log(log_base::platform_ending());
  machine._parent.stripper.make_transition(machine, PRINTING);
  return 0;
}

int cstrip::printing(common_dude &machine)
{
  if (machine.trigger() == NEWLINE) {
    machine._carriage_returns++;
    machine._spaces = 0;
    if (machine._carriage_returns <= 2)
      machine._parent.log(istring(char(machine.trigger()), 1));
  } else {
    if (machine.trigger() != ' ') {
      machine._carriage_returns = 0;
      while (--machine._spaces) machine._parent.log(" ");
      machine._spaces = 0;  // reset after looping.
      machine._parent.log(istring(char(machine.trigger()), 1));
    } else machine._spaces++;
  }
  return 0;
}

int cstrip::one_slash(common_dude &machine)
{
  machine._parent.log(istring(SLASH, 1));
  machine._parent.stripper.make_transition(machine, PRINTING);
  return 0;
}

////////////////////////////////////////////////////////////////////////////

int main(int formal(argc), char *formal(argv)[])
{
  cstrip cs;
  return cs.execute();
}

////////////////////////////////////////////////////////////////////////////

// The following garbage is simply meant as an example to test the stripper.

// this version still screws up when a // or a /* are found within quotes.
// for example:
//hmmm: work this string into a test.
#if 0
static const char *bogus = "// this should still come through /* ho */";
#endif

int yowza(int q) { q--; q++; return q; }

// this is a standard one line comment.
void lo_main()
{
  // followed by some code and another comment.
  int c = 3;
  int terra_x = 5;

/* this is a multi line
comment used to see how things are going to work. */ for (int i = 0;
i < 12;
i++) {  //that was some nice stuff, eh.
   yowza(i + terra_x - c);
}

  /* beep, some really funky something to \
check out for it working right. \
 */

//how's it look?
}

/* and here's some more stuff....*/

int friedroy = 0;   /* this is continued
on the next line, and then a weird bit: */     int tupok = 3;

#ifdef __BUILD_STATIC_APPLICATION__
  // static dependencies found by buildor_gen_deps.sh:
  #include <basis/array.cpp>
  #include <basis/byte_array.cpp>
  #include <basis/callstack_tracker.cpp>
  #include <basis/chaos.cpp>
  #include <basis/convert_utf.cpp>
  #include <basis/definitions.cpp>
  #include <basis/earth_time.cpp>
  #include <basis/guards.cpp>
  #include <basis/istring.cpp>
  #include <basis/log_base.cpp>
  #include <basis/memory_checker.cpp>
  #include <basis/mutex.cpp>
  #include <basis/object_base.cpp>
  #include <basis/outcome.cpp>
  #include <basis/packable.cpp>
  #include <basis/portable.cpp>
  #include <basis/sequence.cpp>
  #include <basis/set.cpp>
  #include <basis/utility.cpp>
  #include <basis/version_record.cpp>
  #include <data_struct/amorph.cpp>
  #include <data_struct/bit_vector.cpp>
  #include <data_struct/byte_hasher.cpp>
  #include <data_struct/configurator.cpp>
  #include <data_struct/hash_table.cpp>
  #include <data_struct/pointer_hash.cpp>
  #include <data_struct/stack.cpp>
  #include <data_struct/static_memory_gremlin.cpp>
  #include <data_struct/string_hash.cpp>
  #include <data_struct/string_hasher.cpp>
  #include <data_struct/string_table.cpp>
  #include <data_struct/symbol_table.cpp>
  #include <data_struct/table_configurator.cpp>
  #include <loggers/console_logger.cpp>
  #include <loggers/file_logger.cpp>
  #include <loggers/locked_logger.cpp>
  #include <loggers/null_logger.cpp>
  #include <loggers/program_wide_logger.cpp>
  #include <mechanisms/mechanisms_implementation_only.cpp>
  #include <mechanisms/state_machine.cpp>
  #include <mechanisms/time_stamp.cpp>
  #include <opsystem/application_base.cpp>
  #include <opsystem/application_shell.cpp>
  #include <opsystem/byte_filer.cpp>
  #include <opsystem/command_line.cpp>
  #include <opsystem/critical_events.cpp>
  #include <opsystem/directory.cpp>
  #include <opsystem/filename.cpp>
  #include <opsystem/ini_config.cpp>
  #include <opsystem/ini_parser.cpp>
  #include <opsystem/path_configuration.cpp>
  #include <opsystem/rendezvous.cpp>
  #include <textual/byte_format.cpp>
  #include <textual/parser_bits.cpp>
  #include <textual/string_manipulation.cpp>
  #include <textual/tokenizer.cpp>
#endif // __BUILD_STATIC_APPLICATION__

