Logo Search packages:      
Sourcecode: uncrustify version File versions

uncrustify.cpp

Go to the documentation of this file.
/**
 * @file uncrustify.cpp
 * This file takes an input C/C++/D/Java file and reformats it.
 *
 * @author  Ben Gardner
 * @license GPL v2+
 *
 * $Id: uncrustify.cpp 705 2007-03-04 22:16:24Z bengardner $
 */
#define DEFINE_PCF_NAMES
#define DEFINE_CHAR_TABLE

#include "uncrustify_version.h"
#include "uncrustify_types.h"
#include "char_table.h"
#include "chunk_list.h"
#include "prototypes.h"
#include "token_names.h"
#include "args.h"
#include "logger.h"
#include "log_levels.h"
#include "md5.h"
#include "backup.h"

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cerrno>
#include <cctype>
#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif
#ifdef HAVE_STRINGS_H
#include <strings.h> /* strcasecmp() */
#endif

/* Global data */
struct cp_data cpd;


static int language_from_tag(const char *tag);
static int language_from_filename(const char *filename);
static const char *language_to_string(int lang);
static char *read_stdin(int& out_len);
static void uncrustify_file(const char *data, int data_len, FILE *pfout,
                            const char *parsed_file);
static void do_source_file(const char *filename, FILE *pfout, const char *parsed_file,
                           const char *prefix, const char *suffix);
static void process_source_list(const char *source_list, const char *prefix, const char *suffix);

/**
 * Replace the brain-dead and non-portable basename().
 * Returns a pointer to the character after the last '/'.
 * The returned value always points into path, unless path is NULL.
 *
 * Input            Returns
 * NULL          => ""
 * "/some/path/" => ""
 * "/some/path"  => "path"
 * "afile"       => "afile"
 *
 * @param path The path to look at
 * @return     Pointer to the character after the last path seperator
 */
00065 const char *path_basename(const char *path)
{
   if (path == NULL)
   {
      return("");
   }

   const char *last_path = path;
   char       ch;

   while ((ch = *path) != 0)
   {
      path++;
      if (ch == '/')
      {
         last_path = path;
      }
   }
   return(last_path);
}

static void usage_exit(const char *msg, const char *argv0, int code)
{
   if (msg != NULL)
   {
      fprintf(stderr, "%s\n", msg);
   }
   if (code != EXIT_SUCCESS)
   {
      fprintf(stderr, "Try running with -h for usage information\n");
      exit(code);
   }
   fprintf(stderr,
           "Usage:\n"
           "%s [options] [files ...]\n"
           "\n"
           "Basic Options:\n"
           " -c CFG       : use the config file CFG\n"
           " -f FILE      : process the single file FILE (output to stdout, use with -o)\n"
           " -o FILE      : Redirect stdout to FILE\n"
           " -F FILE      : read files to process from FILE, one filename per line\n"
           " files        : files to process (can be combined with -F)\n"
           " --suffix SFX : Append SFX to the output filename. The default is '.uncrustify'\n"
           " --prefix PFX : Prepend PFX to the output filename path.\n"
           " --replace    : replace source files (creates a backup)\n"
           " -l           : language override: C, CPP, D, CS, JAVA, PAWN\n"
           " -t           : load a file with types (usually not needed)\n"
           " -q           : quiet mode - no output on stderr (-L will override)\n"
           "\n"
           "Config/Help Options:\n"
           " -h -? --help --usage     : print this message and exit\n"
           " --version                : print the version and exit\n"
           " --show-config            : print out option documentation and exit\n"
           " --update-config          : Output a new config file. Use with -o FILE\n"
           " --update-config-with-doc : Output a new config file. Use with -o FILE\n"
           " --universalindent        : Output a config file for Universal Indent GUI\n"
           "\n"
           "Debug Options:\n"
           " -p FILE      : dump debug info to a file\n"
           " -L SEV       : Set the log severity (see log_levels.h)\n"
           " -s           : Show the log severity in the logs\n"
           " --decode FLAG: Print FLAG as text and exit\n"
           "\n"
           "If no input files are specified, the input is read from stdin\n"
           "If -F is used or files are specified on the command line, the output is 'prefix/filename' + suffix\n"
           "Otherwise, the output is dumped to stdout, unless redirected with -o FILE.\n"
           "Errors are always dumped to stderr\n",
           path_basename(argv0));
   exit(code);
}

static void version_exit(void)
{
   printf("uncrustify %s\n", UNCRUSTIFY_VERSION);
   exit(0);
}

int main(int argc, char *argv[])
{
   char       *data = NULL;
   int        data_len = 0;
   const char *cfg_file    = "uncrustify.cfg";
   const char *parsed_file = NULL;
   const char *source_file = NULL;
   const char *output_file = NULL;
   const char *source_list = NULL;
   log_mask_t mask;
   int        idx;
   const char *p_arg;

   /* If ran without options... show the usage info */
   if (argc == 1)
   {
      usage_exit(NULL, argv[0], EXIT_SUCCESS);
   }

   /* Build options map */
   register_options();

   Args arg(argc, argv);

   if (arg.Present("--version") || arg.Present("-v"))
   {
      version_exit();
   }
   if (arg.Present("--help") || arg.Present("-h") ||
       arg.Present("--usage") || arg.Present("-?"))
   {
      usage_exit(NULL, argv[0], EXIT_SUCCESS);
   }

   if (arg.Present("--show-config"))
   {
      print_options(stdout, true);
      return(0);
   }

#ifdef WIN32
   /* tell windoze not to change what I write to stdout */
   _setmode(_fileno(stdout), _O_BINARY);
#endif

   /* Init logging */
   log_init(stderr);
   if (arg.Present("-q"))
   {
      logmask_from_string("", &mask);
      log_set_mask(&mask);
   }
   if (((p_arg = arg.Param("-L")) != NULL) ||
       ((p_arg = arg.Param("--log")) != NULL))
   {
      logmask_from_string(p_arg, &mask);
      log_set_mask(&mask);
   }

   if ((p_arg = arg.Param("--decode")) != NULL)
   {
      log_pcf_flags(LSYS, strtoul(p_arg, NULL, 16));
      exit(EXIT_SUCCESS);
   }

   /* Get the config file name */
   if (((cfg_file = arg.Param("--config")) == NULL) &&
       ((cfg_file = arg.Param("-c")) == NULL))
   {
#ifdef WIN32
      usage_exit("Specify the config file: -c file", argv[0], 58);
#endif
   }

   /* Get the parsed file name */
   if (((parsed_file = arg.Param("--parsed")) != NULL) ||
       ((parsed_file = arg.Param("-p")) != NULL))
   {
      LOG_FMT(LNOTE, "Will export parsed data to: %s\n", parsed_file);
   }

   /* Enable log sevs? */
   if (arg.Present("-s") || arg.Present("--show"))
   {
      log_show_sev(true);
   }

   /* Load the config file */
   set_option_defaults();

   /* Load type files */
   idx = 0;
   while ((p_arg = arg.Params("-t", idx)) != NULL)
   {
      load_keyword_file(p_arg);
   }

   /* add types */
   idx = 0;
   while ((p_arg = arg.Params("--type", idx)) != NULL)
   {
      add_keyword(p_arg, CT_TYPE, LANG_ALL);
   }

   /* Load define files */
   idx = 0;
   while ((p_arg = arg.Params("-d", idx)) != NULL)
   {
      load_define_file(p_arg);
   }

   /* add defines */
   idx = 0;
   while ((p_arg = arg.Params("--define", idx)) != NULL)
   {
      add_define(p_arg, NULL);
   }

   /* Check for a language override */
   if ((p_arg = arg.Param("-l")) != NULL)
   {
      cpd.lang_flags = language_from_tag(p_arg);
      if (cpd.lang_flags == 0)
      {
         LOG_FMT(LWARN, "Ignoring unknown language: %s\n", p_arg);
      }
   }

   /* Get the source file name */
   if (((source_file = arg.Param("--file")) == NULL) &&
       ((source_file = arg.Param("-f")) == NULL))
   {
      // not using a single file
   }

   if (((source_list = arg.Param("--files")) == NULL) &&
       ((source_list = arg.Param("-F")) == NULL))
   {
      // not using a file list
   }

   const char *prefix = arg.Param("--prefix");
   const char *suffix = arg.Param("--suffix");

   if (arg.Present("--replace"))
   {
      if ((prefix != NULL) || (suffix != NULL))
      {
         usage_exit("Cannot use --replace with --prefix or --suffix", argv[0], 66);
      }
   }
   else
   {
      if ((prefix == NULL) && (suffix == NULL))
      {
         suffix = ".uncrustify";
      }
   }

   /* Grab the output override */
   output_file = arg.Param("-o");

   bool update_config    = arg.Present("--update-config");
   bool update_config_wd = arg.Present("--update-config-with-doc");

   if (arg.Present("--universalindent"))
   {
      FILE *pfile = stdout;

      if (output_file != NULL)
      {
         pfile = fopen(output_file, "w");
         if (pfile == NULL)
         {
            fprintf(stderr, "Unable to open %s for write: %s (%d)\n",
                    output_file, strerror(errno), errno);
            return(EXIT_FAILURE);
         }
      }

      print_universal_indent_cfg(pfile);

      return(EXIT_SUCCESS);
   }

   /*
    *  Done parsing args
    */

   /* Check for unused args (ignore them) */
   idx   = 1;
   p_arg = arg.Unused(idx);

   /* Check args - for multifile options */
   if ((source_list != NULL) || (p_arg != NULL))
   {
      if (source_file != NULL)
      {
         usage_exit("Cannot specify both the single file option and a mulit-file option.",
                    argv[0], 67);
      }

      if (output_file != NULL)
      {
         printf("source_file = %s, p_arg = %s\n", source_file, p_arg);
         usage_exit("Cannot specify -o with a mulit-file option.",
                    argv[0], 68);
      }
   }

#ifndef WIN32
   char buf[512];
   if (cfg_file == NULL)
   {
      cfg_file = getenv("UNCRUSTIFY_CONFIG");
      if (cfg_file == NULL)
      {
         const char *home = getenv("HOME");

         if (home == NULL)
         {
            usage_exit("Specify the config file with '-c file' or set UNCRUSTIFY_CONFIG",
                       argv[0], 58);
         }
         snprintf(buf, sizeof(buf), "%s/.uncrustify.cfg", home);
         cfg_file = buf;
      }
   }
#endif

   cpd.filename = cfg_file;
   if (load_option_file(cfg_file) < 0)
   {
      usage_exit("Unable to load the config file", argv[0], 56);
   }

   /* Reopen stdout */
   FILE *my_stdout = stdout;
   if (output_file != NULL)
   {
      my_stdout = freopen(output_file, "wb", stdout);
      if (my_stdout == NULL)
      {
         LOG_FMT(LERR, "Unable to open %s for write: %s (%d)\n",
                 output_file, strerror(errno), errno);
         usage_exit(NULL, argv[0], 56);
      }
      LOG_FMT(LNOTE, "Redirecting output to %s\n", output_file);
   }

   if (update_config || update_config_wd)
   {
      save_option_file(stdout, update_config_wd);
      return(0);
   }

   if ((source_file == NULL) && (source_list == NULL) && (p_arg == NULL))
   {
      /* no input specified, so use stdin */
      if (cpd.lang_flags == 0)
      {
         cpd.lang_flags = LANG_C;
      }

      data = read_stdin(data_len);
      if (data == NULL)
      {
         LOG_FMT(LERR, "Out of memory\n");
         return(100);
      }

      cpd.filename = "stdin";

      /* Done reading from stdin */
      LOG_FMT(LSYS, "Parsing: %d bytes from stdin as language %s\n",
              data_len, language_to_string(cpd.lang_flags));

      uncrustify_file(data, data_len, stdout, parsed_file);
      free(data);
      data_len = 0;
      data = NULL;
   }
   else if (source_file != NULL)
   {
      /* Doing a single file, output to stdout */
      do_source_file(source_file, stdout, parsed_file, NULL, NULL);
   }
   else
   {
      /* Doing multiple files */
      if (prefix != NULL)
      {
         LOG_FMT(LSYS, "Output prefix: %s/\n", prefix);
      }
      if (suffix != NULL)
      {
         LOG_FMT(LSYS, "Output suffix: %s\n", suffix);
      }

      /* Do the files on the command line first */
      idx = 1;
      while ((p_arg = arg.Unused(idx)) != NULL)
      {
         do_source_file(p_arg, NULL, NULL, prefix, suffix);
      }

      if (source_list != NULL)
      {
         process_source_list(source_list, prefix, suffix);
      }
   }

   clear_keyword_file();
   clear_defines();

   return((cpd.error_count != 0) ? 1 : 0);
}


static void process_source_list(const char *source_list,
                                const char *prefix, const char *suffix)
{
   FILE *p_file = fopen(source_list, "r");

   if (p_file == NULL)
   {
      LOG_FMT(LERR, "%s: fopen(%s) failed: %s (%d)\n",
              __func__, source_list, strerror(errno), errno);
      cpd.error_count++;
      return;
   }

   char linebuf[256];
   int  argc;
   char *args[3];
   int  line = 0;
   int  idx;

   while (fgets(linebuf, sizeof(linebuf), p_file) != NULL)
   {
      line++;
      argc = Args::SplitLine(linebuf, args, ARRAY_SIZE(args));

      LOG_FMT(LFILELIST, "%3d]", line);
      for (idx = 0; idx < argc; idx++)
      {
         LOG_FMT(LFILELIST, " [%s]", args[idx]);
      }
      LOG_FMT(LFILELIST, "\n");

      if ((argc == 1) && (*args[0] != '#'))
      {
         do_source_file(args[0], NULL, NULL, prefix, suffix);
      }
   }
}


static char *read_stdin(int& out_len)
{
   char *data;
   char *new_data;
   int  data_size;
   int  data_len;
   int  len;

   /* Start with 64k */
   data_size = 64 * 1024;
   data      = (char *)malloc(data_size);
   data_len  = 0;

   if (data == NULL)
   {
      return(NULL);
   }

   while ((len = fread(&data[data_len], 1, data_size - data_len, stdin)) > 0)
   {
      data_len += len;
      if (data_len == data_size)
      {
         /* Double the buffer size */
         data_size *= 2;
         if ((new_data = (char *)realloc(data, data_size)) == NULL)
         {
            free(data);
            return(NULL);
         }
         data = new_data;
      }
   }

   assert(data_len < data_size);
   /* Make sure the buffer is terminated */
   data[data_len] = 0;

   out_len = data_len;

   return(data);
}

static void make_folders(char *outname)
{
   int idx;
   int last_idx = 0;

   for (idx = 0; outname[idx] != 0; idx++)
   {
      if ((outname[idx] == '/') || (outname[idx] == '\\'))
      {
         outname[idx] = PATH_SEP;
      }

      if ((idx > last_idx) && (outname[idx] == PATH_SEP))
      {
         outname[idx] = 0;

         if ((strcmp(&outname[last_idx], ".") != 0) &&
             (strcmp(&outname[last_idx], "..") != 0))
         {
            //fprintf(stderr, "%s: %s\n", __func__, outname);
            mkdir(outname, 0750);
         }
         outname[idx] = PATH_SEP;
      }

      if (outname[idx] == PATH_SEP)
      {
         last_idx = idx + 1;
      }
   }
}

/**
 * Does a source file.
 * If pfout is NULL, the source fileaname + ".uncrustify" is used.
 *
 * @param filename the file to read
 * @param pfout    NULL or the output stream
 */
00582 static void do_source_file(const char *filename, FILE *pfout, const char *parsed_file,
                           const char *prefix, const char *suffix)
{
   int         data_len;
   char        *data;
   bool        did_open = false;
   FILE        *p_file;
   struct stat my_stat;
   bool        need_backup = false;

   /* Do some simple language detection based on the filename */
   if (cpd.lang_flags == 0)
   {
      cpd.lang_flags = language_from_filename(filename);
   }

   /* Try to read in the source file */
   p_file = fopen(filename, "rb");
   if (p_file == NULL)
   {
      LOG_FMT(LERR, "%s: fopen(%s) failed: %s (%d)\n",
              __func__, filename, strerror(errno), errno);
      cpd.error_count++;
      return;
   }

   /*note: could have also just MMAP'd the file, if supported */
   fstat(fileno(p_file), &my_stat);

   data_len = my_stat.st_size;
   data     = (char *)malloc(data_len + 1);
   assert(data != NULL);
   fread(data, data_len, 1, p_file);
   data[data_len] = 0;
   fclose(p_file);

   LOG_FMT(LSYS, "Parsing: %s as language %s\n",
           filename, language_to_string(cpd.lang_flags));

   if (pfout == NULL)
   {
      char outname[1024];
      int  len = 0;

      if ((prefix == NULL) && (suffix == NULL))
      {
         if (backup_copy_file(filename, data, data_len) != SUCCESS)
         {
            LOG_FMT(LERR, "%s: Failed to create backup file for %s\n",
                    __func__, filename);
            free(data);
            return;
         }
         need_backup = true;
      }

      if (prefix != NULL)
      {
         len = snprintf(outname, sizeof(outname), "%s/", prefix);
      }

      snprintf(&outname[len], sizeof(outname) - len, "%s%s", filename,
               (suffix != NULL) ? suffix : "");

      make_folders(outname);

      pfout = fopen(outname, "wb");
      if (pfout == NULL)
      {
         LOG_FMT(LERR, "%s: Unable to create %s: %s (%d)\n",
                 __func__, outname, strerror(errno), errno);
         cpd.error_count++;
         free(data);
         return;
      }
      did_open = true;
      //LOG_FMT(LSYS, "Output file %s\n", outname);
   }

   cpd.filename = filename;
   uncrustify_file(data, data_len, pfout, parsed_file);

   free(data);

   if (did_open)
   {
      fclose(pfout);

      if (need_backup)
      {
         backup_create_md5_file(filename);
      }
   }
}


00678 static void uncrustify_file(const char *data, int data_len, FILE *pfout,
                            const char *parsed_file)
{
   /**
    * Parse the text into chunks
    */
   tokenize(data, data_len);

   /**
    * Change certain token types based on simple sequence.
    * Example: change '[' + ']' to '[]'
    * Note that level info is not yet available, so it is OK to do all
    * processing that doesn't need to know level info. (that's very little!)
    */
   tokenize_cleanup();

   /**
    * Detect the brace and paren levels and insert virtual braces.
    * This handles all that nasty preprocessor stuff
    */
   brace_cleanup();

   /**
    * At this point, the level information is available and accurate.
    */

   if ((cpd.lang_flags & LANG_PAWN) != 0)
   {
      pawn_prescan();
   }

   /**
    * Re-type chunks, combine chunks
    */
   fix_symbols();

   /**
    * Look at all colons ':' and mark labels, :? sequences, etc.
    */
   combine_labels();

   /**
    * Change virtual braces into real braces...
    */
   do_braces();

   /**
    * Add parens
    */
   do_parens();

   /**
    * Insert line breaks as needed
    */
   do_blank_lines();
   newlines_cleanup_braces();
   newlines_insert_blank_lines();
   if (cpd.settings[UO_nl_ds_struct_enum_cmt].b)
   {
      newlines_double_space_struct_enum_union();
   }
   if (cpd.settings[UO_nl_squeeze_ifdef].b)
   {
      newlines_squeeze_ifdef();
   }
   if (cpd.settings[UO_pos_bool].tp != TP_IGNORE)
   {
      newlines_bool_pos();
   }
   newlines_class_colon_pos();
   newlines_eat_start_end();
   newlines_cleanup_dup();

   mark_comments();

   /**
    * Add balanced spaces around nested params
    */
   if (cpd.settings[UO_sp_balance_nested_parens].b)
   {
      space_text_balance_nested_parens();
   }

   /**
    * Fix same-line inter-chunk spacing
    */
   space_text();

   /**
    * Do any aligning of preprocessors
    */
   if (cpd.settings[UO_align_pp_define_span].n > 0)
   {
      align_preprocessor();
   }

   /**
    * Indent the text
    */
   indent_text();
   indent_preproc();

   /**
    * Aligning everything else and reindent
    */
   align_all();
   indent_text();

   if (cpd.settings[UO_code_width].n > 0)
   {
      int max_passes = 3;
      int prev_changes;
      do
      {
         prev_changes = cpd.changes;
         do_code_width();
         if (prev_changes != cpd.changes)
         {
            indent_text();
         }
      } while ((prev_changes != cpd.changes) && (--max_passes > 0));
   }

   /**
    * And finally, align the backslash newline stuff
    */
   align_right_comments();
   if (cpd.settings[UO_align_nl_cont].b)
   {
      align_backslash_newline();
   }

   /**
    * Now render it all to the output file
    */
   output_text(pfout);

   /* Special hook for dumping parsed data for debugging */
   if (parsed_file != NULL)
   {
      FILE *p_file = fopen(parsed_file, "w");
      if (p_file != NULL)
      {
         output_parsed(p_file);
         fclose(p_file);
      }
   }

   /* Free all the memory */
   chunk_t *pc;
   while ((pc = chunk_get_head()) != NULL)
   {
      chunk_del(pc);
   }
}


const char *get_token_name(c_token_t token)
{
   if ((token >= 0) && (token < (int)ARRAY_SIZE(token_names)) &&
       (token_names[token] != NULL))
   {
      return(token_names[token]);
   }
   return("???");
}


static bool ends_with(const char *filename, const char *tag)
{
   int len1 = strlen(filename);
   int len2 = strlen(tag);

   if ((len2 <= len1) && (strcmp(&filename[len1 - len2], tag) == 0))
   {
      return(true);
   }
   return(false);
}


struct file_lang
{
   const char *ext;
   const char *tag;
   int        lang;
};

struct file_lang languages[] =
{
   { ".c",    "C",    LANG_C    },
   { ".cpp",  "CPP",  LANG_CPP  },
   { ".d",    "D",    LANG_D    },
   { ".cs",   "CS",   LANG_CS   },
   { ".java", "JAVA", LANG_JAVA },
   { ".pawn", "PAWN", LANG_PAWN },
   { ".p",    "",     LANG_PAWN },
   { ".sma",  "",     LANG_PAWN },
   { ".inl",  "",     LANG_PAWN },
   { ".h",    "",     LANG_CPP  },
   { ".cxx",  "",     LANG_CPP  },
   { ".hpp",  "",     LANG_CPP  },
   { ".hxx",  "",     LANG_CPP  },
   { ".cc",   "",     LANG_CPP  },
   { ".di",   "",     LANG_D    },
   { ".m",    "OC",   LANG_OC   },
   { ".sqc",  "",     LANG_C    }, // embedded SQL
};

/**
 * Set idx = 0 before the first call.
 * Done when returns NULL
 */
00891 const char *get_file_extension(int &idx)
{
   const char *val = NULL;

   if (idx < (int)ARRAY_SIZE(languages))
   {
      val = languages[idx].ext;
   }
   idx++;
   return(val);
}


/**
 * Find the language for the file extension
 * Default to C
 *
 * @param filename   The name of the file
 * @return           LANG_xxx
 */
00911 static int language_from_filename(const char *filename)
{
   int i;

   for (i = 0; i < (int)ARRAY_SIZE(languages); i++)
   {
      if (ends_with(filename, languages[i].ext))
      {
         return(languages[i].lang);
      }
   }
   return(LANG_C);
}


/**
 * Find the language for the file extension
 *
 * @param filename   The name of the file
 * @return           LANG_xxx or 0 (no match)
 */
00932 static int language_from_tag(const char *tag)
{
   int i;

   for (i = 0; i < (int)ARRAY_SIZE(languages); i++)
   {
      if (strcasecmp(tag, languages[i].tag) == 0)
      {
         return(languages[i].lang);
      }
   }
   return(0);
}


/**
 * Gets the tag text for a language
 *
 * @param lang    The LANG_xxx enum
 * @return        A string
 */
00953 static const char *language_to_string(int lang)
{
   int i;

   for (i = 0; i < (int)ARRAY_SIZE(languages); i++)
   {
      if ((languages[i].lang & lang) != 0)
      {
         return(languages[i].tag);
      }
   }
   return("???");
}

void log_pcf_flags(log_sev_t sev, UINT32 flags)
{
   if (!log_sev_on(sev))
   {
      return;
   }

   log_fmt(sev, "[0x%X:", flags);

   const char *tolog = NULL;
   for (int i = 0; i < (int)ARRAY_SIZE(pcf_names); i++)
   {
      if ((flags & (1 << i)) != 0)
      {
         if (tolog != NULL)
         {
            log_str(sev, tolog, strlen(tolog));
            log_str(sev, ",", 1);
         }
         tolog = pcf_names[i];
      }
   }

   if (tolog != NULL)
   {
      log_str(sev, tolog, strlen(tolog));
   }

   log_str(sev, "]\n", 2);
}

Generated by  Doxygen 1.6.0   Back to index