/*
 * Copyright (c) 2002, The Tendra Project <http://www.ten15.org/>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice unmodified, this list of conditions, and the following
 *    disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *
 *    		 Crown Copyright (c) 1997
 *
 *    This TenDRA(r) Computer Program is subject to Copyright
 *    owned by the United Kingdom Secretary of State for Defence
 *    acting through the Defence Evaluation and Research Agency
 *    (DERA).  It is made available to Recipients with a
 *    royalty-free licence for its use, reproduction, transfer
 *    to other parties and amendment for any purpose not excluding
 *    product development provided that any such use et cetera
 *    shall be deemed to be acceptance of the following conditions:-
 *
 *        (1) Its Recipients shall ensure that this Notice is
 *        reproduced upon any copies or amended versions of it;
 *
 *        (2) Any amended version of it shall be clearly marked to
 *        show both the nature of and the organisation responsible
 *        for the relevant amendment or amendments;
 *
 *        (3) Its onward transfer from a recipient to another
 *        party shall be deemed to be that party's acceptance of
 *        these conditions;
 *
 *        (4) DERA gives no warranty or assurance as to its
 *        quality or suitability for any purpose and DERA accepts
 *        no liability whatsoever in relation to any use to which
 *        it may be put.
 *
 * $TenDRA: tendra/src/tools/tspec/index.c,v 1.8 2004/08/08 08:50:20 stefanf Exp $
 */


#include "config.h"
#include "fmm.h"
#include "msgcat.h"

#include "object.h"
#include "hash.h"
#include "name.h"
#include "type.h"
#include "print.h"
#include "utility.h"


/*
 *    FLAGS
 *
 *    These flags are used to indicate various output states indicated by
 *    preprocessing directives.  A value of 1 is the default (which
 *    actually means that the condition is false), 2 means that the given
 *    statement is true, and 0 means that its negation is true.
 */

static int building_libs = 1;
static int commented_out = 1;


/*
 *    FIELD SEPARATOR
 *
 *    The field separator for the machine processable index.  This
 *    separator can be changed, but no command line option is provided to
 *    do this as '$' seems ideal.
 */

static char field_sep = '$';


/*
 *    PRINT FIELD SEPARATOR
 *
 *    Routine to print field separator of the machine processable index.
 */

#define print_field_sep()	IGNORE putchar (field_sep)


/*
 *    PRINT FIELD
 *
 *    Routine to print field and separator of the machine processable index.
 */

static void
print_field(char *s)
{
	IGNORE printf ("%s%c", s, field_sep);
	return;
}


/*
 *    PRINT ESCAPED FIELD
 *
 *    Routine to print field without separator of the machine processable
 *    index, escaping characters that could confuse output processing tools.
 */

static void
print_escape(char *s)
{
	int c;
	while ((c = *s++) != '\0') {
		if (c == field_sep) {
			IGNORE printf ("\\F");
		} else if (c == '\n') {
			IGNORE printf ("\\n");
		} else if (c == '\\') {
			IGNORE printf ("\\\\");
		} else {
			IGNORE putchar (c);
		}
    }
    return;
}


/*
 *    PRINT VALUE FIELD
 *
 *    Routine to print the final value field of the machine processable index.
 */

static void
print_value(char *s)
{
    print_field_sep ();
    print_escape (s);
    IGNORE putchar ('\n');
    return;
}


/*
 *    PRINT EMPTY VALUE FIELD
 *
 *    Routine to print the final empty value field of the machine processable
 *    index.
 */

static void
print_no_value(void)
{
    IGNORE printf ("%c\n", field_sep);
    return;
}


/*
 *    PRINT SORT, INFO AND TYPE FIELDS
 *
 *    Routine to print sort, info and type fields of the machine processable
 *    index.
 */

static void
print_sit_v(char *s, char *i, type *t, char *nm)
{
    IGNORE printf ("%s%c%s%c", s, field_sep, i, field_sep);
    IGNORE print_type (stdout, t, nm, 0);
    return;
}


/*
 *    PRINT SORT AND TYPE FIELDS
 *
 *    Routine to print sort, empty info, and type fields of the machine
 *    processable index.
 */

static void
print_st_v(char *s, type *t, char *nm)
{
    print_sit_v (s, "", t, nm);
    return;
}


/*
 *    PRINT SORT AND INFO FIELDS
 *
 *    Routine to print sort, info, and empty type fields of the machine
 *    processable index.
 */

static void
print_si_v(char *s, char *i)
{
    IGNORE printf ("%s%c%s%c", s, field_sep, i, field_sep);
    return;
}


/*
 *    PRINT SORT FIELD
 *
 *    Routine to print sort, empty info, and empty type fields of the
 *    machine processable index.
 */

static void
print_s_v(char *s)
{
    IGNORE printf ("%s%c%c", s, field_sep, field_sep);
    return;
}


/*
 *    PRINT SORT, INFO, TYPE AND EMPTY VALUE FIELDS
 *
 *    Routine to print sort, info, type and empty value fields of the
 *    machine processable index.
 */

static void
print_sit(char *s, char *i, type *t, char *nm)
{
    print_sit_v (s, i, t, nm);
    print_no_value ();
    return;
}


/*
 *    PRINT SORT, TYPE AND EMPTY VALUE FIELDS
 *
 *    Routine to print sort, empty info, type and empty value fields of the
 *    machine processable index.
 */

static void
print_st(char *s, type *t, char *nm)
{
    print_st_v (s, t, nm);
    print_no_value ();
    return;
}


/*
 *    PRINT SORT, INFO AND EMPTY VALUE FIELDS
 *
 *    Routine to print sort, info, empty type and empty value fields of the
 *    machine processable index.
 */

static void
print_si(char *s, char *i)
{
    print_si_v (s, i);
    print_no_value ();
    return;
}


/*
 *    PRINT SORT AND EMPTY VALUE FIELDS
 *
 *    Routine to print sort, empty info, empty type and empty value fields
 *    of the machine processable index.
 */

static void
print_s(char *s)
{
    print_s_v (s);
    print_no_value ();
    return;
}


/*
 *    IF STACK STATE
 *
 *    This stack is used to keep track of the current +IF conditions.
 */

static object **if_stack = 0;
static int if_stack_sz = 0;
static int if_stack_index = 0;


/*
 *    STACK AN IF COMMAND OBJECT
 *
 *    Routine to stack an object representing +IFxxx or +ELSE.
 */

static void
stack_if (object *p)
{
    if (if_stack_index == if_stack_sz) {
		if_stack_sz += 16;
		if_stack = xrealloc (if_stack, sizeof (*p) * if_stack_sz);
    }
    if_stack [if_stack_index] = p;
    if_stack_index++;
    return;
}


/*
 *    UNSTACK AN IF COMMAND OBJECT
 *
 *    Routine to unstack an object representing +IFxxx or +ELSE.
 */

static object *
unstack_if(void)
{
    return (if_stack [--if_stack_index]);
}


/*
 *    PRINT IF NESTING
 *
 *    Routine to print the currently stacked +IFxxx and +ELSE nesting.
 */

static void
print_if_nest(void)
{
    int i;
    for (i = 0; i < if_stack_index; i++) {
		char code;
		object *p = if_stack [i];
		char *c = p->name;

		if (i > 0) print_escape (";");
		if (i + 1 < if_stack_index &&
			if_stack [i + 1]->u.u_num == CMD_ELSE) {
			IGNORE printf ("e");
			i++;
		}
		switch (p->u.u_num) EXHAUSTIVE {
	    case CMD_IF : code = 'i'; break;
	    case CMD_IFDEF : code = 'd'; break;
	    case CMD_IFNDEF : code = 'n'; break;
		}
		IGNORE printf ("%c", code);
		print_escape (":");
		print_escape (c);
    }
    return;
}


/*
 *    PRINT A MACHINE PROCESSABLE ITEM INDEX
 *
 *    This routine prints the index item indicated by the token object p.
 *    u gives the token status, a the current file name, and e is used in
 *    enumeration items.  The output is in fields suitable for machine
 *    processing by tools such as 'awk'.
 */

static void
print_item_m(object *p, char *u, char *a, type *e)
{
    char *nm;
    char *ap;
    char *tnm = p->name;
    object *q = p->u.u_obj;

    if (q->objtype == OBJ_FIELD) {
		nm = q->u.u_field->fname;
    } else {
		nm = q->name;
    }

    /* Field 1: C_SYMBOL */
    print_field (nm);

    /* Field 2: TOKEN */
    print_field (tnm);

    /* Field 3: STATUS */
    IGNORE printf ("%c%c", u [0], field_sep);

    /* Field 4: IF_NESTING */
    print_if_nest ();
    print_field_sep ();

    /* Field 5: API_LOCATION */
    for (ap = a; *ap && *ap != ':'; ap++) IGNORE putchar (*ap);
    print_field_sep ();

    /* Field 6: FILE_LOCATION */
    if (*ap) ap++;
    for (; *ap && *ap != ':'; ap++) IGNORE putchar (*ap);
    print_field_sep ();

    /* Field 7: LINE_LOCATION */
    IGNORE printf ("%d%c", q->line_no, field_sep);

    /* Field 8: SUBSET_NESTING */
    if (*ap) IGNORE printf ("%s", ap + 1);
    print_field_sep ();

    /* Fields 9-12: SORT, INFO, TYPE, VALUE */
    switch (q->objtype) {

	case OBJ_CONST : {
	    print_st ("const", q->u.u_type, NULL);
	    break;
	}

	case OBJ_ENUMVAL : {
	    print_field ("enum_member");
	    print_type (stdout, e, NULL, 0);
	    print_field_sep ();
	    if (q->u.u_str) {
			print_value (q->u.u_str);
	    } else {
			print_no_value ();
	    }
	    break;
	}

	case OBJ_EXP : {
	    type *t = q->u.u_type;
	    char *s = (t->id == TYPE_LVALUE ? "lvalue" : "rvalue");
	    print_st (s, t, NULL);
	    break;
	}

	case OBJ_EXTERN : {
	    type *t = q->u.u_type;
	    if (t->id == TYPE_LVALUE) t = t->u.subtype;
	    if (t->id == TYPE_PROC) {
			print_sit ("func", "extern", t, nm);
	    } else {
			print_st ("extern", t, NULL);
	    }
	    break;
	}

	case OBJ_WEAK : {
	    type *t = q->u.u_type;
	    print_sit ("func", "weak", t, nm);
	    break;
	}

	case OBJ_DEFINE : {
	    char *s = q->u.u_str;
	    if (*s == '(') {
			print_field ("define");
			print_field ("param");
			for (; *s && *s != ')'; s++) {
				IGNORE putchar (*s);
			}
			if (*s == ')') s++;
			IGNORE printf (")");
	    } else {
			print_s_v ("define");
	    }
	    while (*s == ' ') s++;
	    print_value (s);
	    break;
	}

	case OBJ_FIELD : {
	    field *f = q->u.u_field;
	    print_field ("member");
	    print_type (stdout, f->stype, NULL, 0);
	    print_field_sep ();
	    print_type (stdout, f->ftype, NULL, 0);
	    print_no_value ();
	    break;
	}

	case OBJ_FUNC : {
	    print_st ("func", q->u.u_type, nm);
	    break;
	}

	case OBJ_MACRO : {
	    print_st ("macro", q->u.u_type, nm);
	    break;
	}

	case OBJ_NAT : {
	    print_s ("nat");
	    break;
	}

	case OBJ_STATEMENT : {
	    type *t = q->u.u_type;
	    if (t) {
			print_sit ("statement", "param", t, NULL);
	    } else {
			print_s ("statement");
	    }
	    break;
	}

	case OBJ_TOKEN : {
	    print_s_v ("token");
	    print_value (q->u.u_str);
	    break;
	}

	case OBJ_TYPE : {
	    type *t = q->u.u_type;
	    int i = t->id;
	    switch (i) {

		case TYPE_DEFINED : {
		    print_st ("typedef", t->v.next, NULL);
		    break;
		}

		case TYPE_GENERIC : {
		    print_s ("opaque");
		    break;
		}

		case TYPE_INT : {
		    print_s ("integral");
		    break;
		}

		case TYPE_SIGNED : {
		    print_s ("signed_integral");
		    break;
		}

		case TYPE_UNSIGNED : {
		    print_s ("unsigned_integral");
		    break;
		}

		case TYPE_PROMOTE : {
		    print_field ("promotion");
		    print_type (stdout, t->v.next, NULL, 0);
		    print_field_sep ();
		    print_no_value ();
		    break;
		}

		case TYPE_FLOAT : {
		    print_s ("floating");
		    break;
		}

		case TYPE_ARITH : {
		    print_s ("arithmetic");
		    break;
		}

		case TYPE_SCALAR : {
		    print_s ("scalar");
		    break;
		}

		case TYPE_STRUCT :
		case TYPE_STRUCT_TAG :
		case TYPE_UNION :
		case TYPE_UNION_TAG :
		case TYPE_ENUM :
		case TYPE_ENUM_TAG : {
		    char *s;
		    type *en = null;
		    object *r = t->v.obj2;
		    char *inf = (r ? "exact" : "");
		    switch (i) EXHAUSTIVE {
			case TYPE_STRUCT : s = "struct"; break;
			case TYPE_STRUCT_TAG : s = "struct_tag"; break;
			case TYPE_UNION : s = "union"; break;
			case TYPE_UNION_TAG : s = "union_tag"; break;
			case TYPE_ENUM : s = "enum"; en = t; break;
			case TYPE_ENUM_TAG : s = "enum_tag"; en = t; break;
		    }
		    print_si (s, inf);
		    while (r) {
				print_item_m (r, u, a, en);
				r = r->next;
		    }
		    break;
		}

		default : {
		    MSG_unknown_type_identifier (i);
		    break;
		}
	    }
	    break;
	}

	default : {
	    MSG_unknown_object_type (q->objtype);
	    break;
	}
    }
    return;
}


/*
 *    PRINT AN INDEX ITEM
 *
 *    This routine prints the index item indicated by the token object p.
 *    u gives the token status, a the current file name, and e is used in
 *    enumeration items.  The output is in a humun readable format.
 */

static void
print_item_h(object *p, char *u, char *a, type *e)
{
    char *tnm = p->name;
    object *q = p->u.u_obj;
    char *nm = q->name;
    IGNORE printf ("TOKEN: %s\n", tnm);
    IGNORE printf ("STATUS: %s", u);
    if (building_libs == 0) IGNORE printf (" (not library building)");
    if (building_libs == 2) IGNORE printf (" (library building only)");
    IGNORE printf ("\nDEFINED: %s, line %d\n", a, q->line_no);
    IGNORE printf ("INFO: ");
    if (commented_out == 2) IGNORE printf ("(commented out) ");

    switch (q->objtype) {

	case OBJ_CONST : {
	    IGNORE printf ("%s is a constant expression of type ", nm);
	    print_type (stdout, q->u.u_type, NULL, 0);
	    IGNORE printf ("\n\n");
	    break;
	}

	case OBJ_ENUMVAL : {
	    IGNORE printf ("%s is a member of the enumeration type ", nm);
	    print_type (stdout, e, NULL, 0);
	    IGNORE printf ("\n\n");
	    break;
	}

	case OBJ_EXP : {
	    IGNORE printf ("%s is an expression of type ", nm);
	    print_type (stdout, q->u.u_type, NULL, 0);
	    IGNORE printf ("\n\n");
	    break;
	}

	case OBJ_EXTERN : {
	    type *t = q->u.u_type;
	    if (t->id == TYPE_LVALUE) t = t->u.subtype;
	    IGNORE printf ("%s is an external ", nm);
	    if (t->id == TYPE_PROC) {
			IGNORE printf ("function with prototype ");
			print_type (stdout, t, nm, 0);
	    } else {
			IGNORE printf ("expression with type ");
			print_type (stdout, t, NULL, 0);
	    }
	    IGNORE printf ("\n\n");
	    break;
	}

	case OBJ_WEAK : {
	    type *t = q->u.u_type;
	    IGNORE printf ("%s is an external ", nm);
	    IGNORE printf ("function with weak prototype ");
	    print_type (stdout, t, nm, 0);
	    IGNORE printf ("\n\n");
	    break;
	}

	case OBJ_DEFINE : {
	    char *s = q->u.u_str;
	    IGNORE printf ("%s is a macro ", nm);
	    if (*s == '(') {
			IGNORE printf ("with arguments ");
			for (; *s && *s != ')'; s++) {
				IGNORE putchar (*s);
			}
			if (*s == ')') s++;
			IGNORE printf (") ");
	    }
	    while (*s == ' ') s++;
	    IGNORE printf ("defined to be %s\n\n", s);
	    break;
	}

	case OBJ_FIELD : {
	    field *f = q->u.u_field;
	    IGNORE printf ("%s is a field selector of ", f->fname);
	    print_type (stdout, f->stype, NULL, 0);
	    IGNORE printf (" of type ");
	    print_type (stdout, f->ftype, NULL, 0);
	    IGNORE printf ("\n\n");
	    break;
	}

	case OBJ_FUNC : {
	    IGNORE printf ("%s is a function with prototype ", nm);
	    print_type (stdout, q->u.u_type, nm, 0);
	    IGNORE printf ("\n\n");
	    break;
	}

	case OBJ_MACRO : {
	    IGNORE printf ("%s is a macro with prototype ", nm);
	    print_type (stdout, q->u.u_type, nm, 0);
	    IGNORE printf ("\n\n");
	    break;
	}

	case OBJ_NAT : {
	    IGNORE printf ("%s is a constant integer\n\n", nm);
	    break;
	}

	case OBJ_STATEMENT : {
	    type *t = q->u.u_type;
	    IGNORE printf ("%s is a statement", nm);
	    if (t) {
			IGNORE printf (" with arguments");
			print_type (stdout, t, NULL, 0);
	    }
	    IGNORE printf ("\n\n");
	    break;
	}

	case OBJ_TOKEN : {
	    IGNORE printf ("%s is a complex token\n\n", nm);
	    break;
	}

	case OBJ_TYPE : {
	    type *t = q->u.u_type;
	    int i = t->id;
	    print_type (stdout, t, NULL, 0);
	    switch (i) {

		case TYPE_DEFINED : {
		    IGNORE printf (" is a type defined to be ");
		    print_type (stdout, t->v.next, NULL, 0);
		    IGNORE printf ("\n\n");
		    break;
		}

		case TYPE_GENERIC : {
		    IGNORE printf (" is a type\n\n");
		    break;
		}

		case TYPE_INT : {
		    IGNORE printf (" is an integral type\n\n");
		    break;
		}

		case TYPE_SIGNED : {
		    IGNORE printf (" is a signed integral type\n\n");
		    break;
		}

		case TYPE_UNSIGNED : {
		    IGNORE printf (" is an unsigned integral type\n\n");
		    break;
		}

		case TYPE_PROMOTE : {
		    IGNORE printf (" is the integral promotion of ");
		    print_type (stdout, t->v.next, NULL, 0);
		    IGNORE printf ("\n\n");
		    break;
		}

		case TYPE_FLOAT : {
		    IGNORE printf (" is a floating type\n\n");
		    break;
		}

		case TYPE_ARITH : {
		    IGNORE printf (" is an arithmetic type\n\n");
		    break;
		}

		case TYPE_SCALAR : {
		    IGNORE printf (" is a scalar type\n\n");
		    break;
		}

		case TYPE_STRUCT :
		case TYPE_STRUCT_TAG :
		case TYPE_UNION :
		case TYPE_UNION_TAG : {
		    char *n;
		    object *r = t->v.obj2;
		    switch (i) EXHAUSTIVE {
			case TYPE_STRUCT : n = "structure"; break;
			case TYPE_STRUCT_TAG : n = "structure"; break;
			case TYPE_UNION : n = "union"; break;
			case TYPE_UNION_TAG : n = "union"; break;
		    }
		    if (r == null) {
				IGNORE printf (" is an inexact %s type\n\n", n);
		    } else {
				IGNORE printf (" is an exact %s type\n\n", n);
				while (r) {
					print_item_h (r, u, a, (type *) null);
					r = r->next;
				}
		    }
		    break;
		}

		case TYPE_ENUM :
		case TYPE_ENUM_TAG : {
		    object *r = t->v.obj2;
		    IGNORE printf (" is an enumeration type\n\n");
		    while (r) {
				print_item_h (r, u, a, t);
				r = r->next;
		    }
		    break;
		}

		default : {
		    IGNORE printf (" is a type\n\n");
		    MSG_unknown_type_identifier (i);
		    break;
		}
	    }
	    break;
	}

	default : {
	    MSG_unknown_object_type (q->objtype);
	    break;
	}
    }
    return;
}


/*
 *    PRINT INDEX USING PRINT ITEM FUNCTION
 *
 *    This routine prints an index of the set object input using fn.
 */

typedef void (*index_func)(object *, char *, char *, type *);

static void
print_index_with(object *input, index_func fn)
{
    object *p = input->u.u_obj;
    info *i = p->u.u_info;
    char *a = p->name;
    char *u = (i->implemented ? "implemented" : "used");
    for (p = i->elements; p != null; p = p->next) {
		switch (p->objtype) {

	    case OBJ_IF : {
			/* Deal with preprocessing directives */
			char *c = p->name;
			if (fn == print_item_m) {
				switch (p->u.u_num) {
				case CMD_IF :
				case CMD_IFDEF :
				case CMD_IFNDEF :
				case CMD_ELSE : {
					stack_if (p);
					break;
				}
				case CMD_ENDIF : {
					if (unstack_if ()->u.u_num == CMD_ELSE) {
						IGNORE unstack_if ();
					}
					break;
				}
				}
			} else if (streq (c, BUILDING_MACRO)) {
				/* Check for the building_libs macro */
				switch (p->u.u_num) {
				case CMD_IF :
				case CMD_IFDEF : {
					building_libs = 2;
					break;
				}
				case CMD_IFNDEF : {
					building_libs = 0;
					break;
				}
				case CMD_ELSE : {
					building_libs = 2 - building_libs;
					break;
				}
				case CMD_ENDIF : {
					building_libs = 1;
					break;
				}
				}
			} else {
				/* Check for integers */
				int n = 0;
				while (*c == '-') c++;
				while (*c >= '0' && *c <= '9') {
					n = 10 * n + (*c - '0');
					c++;
				}
				if (*c == 0) {
					switch (p->u.u_num) {
					case CMD_IF : {
						commented_out = (n ? 0 : 2);
						break;
					}
					case CMD_ELSE : {
						commented_out = 2 - commented_out;
						break;
					}
					case CMD_ENDIF : {
						commented_out = 1;
						break;
					}
					}
				}
			}
			break;
	    }

	    case OBJ_SET : {
			/* Deal with subsets */
			print_index_with (p, fn);
			break;
	    }

	    case OBJ_TOKEN : {
			/* Deal with tokens */
			if (i->implemented || !restrict_use) {
				(*fn) (p, u, a, (type *) null);
			}
			break;
	    }
		}
    }
    return;
}


/*
 *    PRINT MACHINE PROCESSABLE INDEX
 *
 *    This routine prints an index intended for machine processing of the
 *    set object input.
 */

void
print_machine_index(object *input)
{
    print_index_with (input, print_item_m);
    return;
}


/*
 *    PRINT INDEX
 *
 *    This routine prints an index intended for human readers of the set
 *    object input.
 */

void
print_index(object *input)
{
    print_index_with (input, print_item_h);
    return;
}


syntax highlighted by Code2HTML, v. 0.9.1