/*
 * 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/print.c,v 1.12 2004/08/08 08:50:21 stefanf Exp $
 */


#include "config.h"
#include "cstring.h"
#include "msgcat.h"
#include "tenapp.h"

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


/*
 *    OUTPUT MACROS
 *
 *    These macros are used as convenient shorthands for various print
 *    routines.
 */

#define OUT		IGNORE fprintf
#define OUTC(X, Y)	IGNORE fputc (Y, X)
#define OUTS(X, Y)	IGNORE fputs (Y, X)


/*
 *    OUTPUT TRICKS
 *
 *    A number of minor tricks are required in the headers, mostly due to
 *    library building problems.
 */

static boolean weak_proto = 0;
#define enum_hack	"__enum_"
#define is_hidden(X)	strneq (X, HIDDEN_NAME, HIDDEN_LEN)


/*
 *    OUTPUT FILE
 *
 *    These variables hold information about the current output file.
 */

static info *crt_info = null;
static int column = 0;


/*
 *    DOES A TYPE HAVE A TAIL COMPONENT?
 *
 *    This routine checks whether the type t has an array, bitfield or
 *    function component.
 */

static int
is_tailed_type(type *t)
{
	if (t) {
		switch (t->id) {
	    case TYPE_ARRAY :
	    case TYPE_BITFIELD :
	    case TYPE_PROC : {
			return (1);
	    }
		}
	}
	return (0);
}


/*
 *    PRINT THE HEAD OF A TYPE
 *
 *    This routine prints the head of the type t, that is to say the base type
 *    and the pointer components, to the file output.
 */

static int
print_head(FILE *output, type *t, int sp, int tok)
{
	if (t == null) return (sp);
	switch (t->id) {
	case TYPE_VOID :
	case TYPE_INT :
	case TYPE_SIGNED :
	case TYPE_UNSIGNED :
	case TYPE_FLOAT :
	case TYPE_ARITH :
	case TYPE_SCALAR :
	case TYPE_STRUCT :
	case TYPE_UNION :
	case TYPE_ENUM :
	case TYPE_GENERIC :
	case TYPE_DEFINED :
	case TYPE_PROMOTE : {
	    OUTS (output, t->u.obj->name);
	    sp = 1;
	    break;
	}
	case TYPE_STRUCT_TAG : {
	    OUT (output, "struct %s", t->u.obj->name);
	    sp = 1;
	    break;
	}
	case TYPE_UNION_TAG : {
	    OUT (output, "union %s", t->u.obj->name);
	    sp = 1;
	    break;
	}
	case TYPE_ENUM_TAG : {
	    if (tok) {
			OUT (output, "%s%s", enum_hack, t->u.obj->name);
	    } else {
			OUT (output, "enum %s", t->u.obj->name);
	    }
	    sp = 1;
	    break;
	}
	case TYPE_LVALUE : {
	    OUTS (output, "lvalue ");
	    if (tok) OUTS (output, ": ");
	    sp = print_head (output, t->u.subtype, 0, tok);
	    break;
	}
	case TYPE_QUALIFIER : {
	    OUT (output, "%s ", t->v.str);
	    sp = print_head (output, t->u.subtype, 0, tok);
	    break;
	}
	case TYPE_RVALUE : {
	    if (tok) OUTS (output, "rvalue : ");
	    sp = print_head (output, t->u.subtype, 0, tok);
	    break;
	}
	case TYPE_PTR : {
	    type *s = t->u.subtype;
	    char *q = t->v.str;
	    sp = print_head (output, s, sp, tok);
	    if (sp) OUTC (output, ' ');
	    if (is_tailed_type (s)) {
			OUTS (output, "(*");
	    } else {
			OUTS (output, "*");
	    }
	    sp = 0;
	    if (q) {
			OUT (output, " %s", q);
			sp = 1;
	    }
	    break;
	}
	case TYPE_ARRAY :
	case TYPE_BITFIELD :
	case TYPE_PROC : {
	    sp = print_head (output, t->u.subtype, sp, tok);
	    break;
	}
	default : {
	    /* Unknown types */
	    MSG_unknown_type_identifier (t->id);
	    break;
	}
	}
	return (sp);
}


/*
 *    PRINT THE TAIL OF A TYPE
 *
 *    This routine prints the tail of the type t, that is to say the array,
 *    bitfield and function components, to the file output.
 */

static void
print_tail(FILE *output, type *t, int tok)
{
	if (t == null) return;
	switch (t->id) {
	case TYPE_LVALUE :
	case TYPE_RVALUE :
	case TYPE_QUALIFIER : {
	    print_tail (output, t->u.subtype, tok);
	    break;
	}
	case TYPE_PTR : {
	    type *s = t->u.subtype;
	    if (is_tailed_type (s)) {
			OUTS (output, ")");
	    }
	    print_tail (output, s, tok);
	    break;
	}
	case TYPE_ARRAY : {
	    OUT (output, " [%s]", t->v.str);
	    print_tail (output, t->u.subtype, tok);
	    break;
	}
	case TYPE_BITFIELD : {
	    if (tok) {
			OUT (output, " %% %s", t->v.str);
	    } else {
			OUT (output, " : %s", t->v.str);
	    }
	    print_tail (output, t->u.subtype, tok);
	    break;
	}
	case TYPE_PROC : {
	    type *s = t->v.next;
	    if (s) {
			OUTS (output, " (");
			while (s) {
				print_type (output, s->u.subtype, NULL, tok);
				s = s->v.next;
				if (s) OUTS (output, ", ");
			}
			OUTS (output, ")");
	    } else {
			OUTS (output, " ()");
	    }
	    print_tail (output, t->u.subtype, tok);
	    break;
	}
	}
	return;
}


/*
 *    PRINT A TYPE
 *
 *    This routine prints the object nm of type t to the file output.
 */

void
print_type(FILE *output, type *t, char *nm, int tok)
{
	if (t) {
		int sp = print_head (output, t, 0, tok);
		if (nm) {
			if (sp) OUTC (output, ' ');
			OUTS (output, nm);
		}
		print_tail (output, t, tok);
	}
	return;
}


/*
 *    PRINT A STRUCT OR UNION DEFINITION
 *
 *    This routine prints the specification for a structure or union type,
 *    t, with internal name nm and external name tnm, to output.
 */

static void
print_struct_defn(FILE *output, type *t, char *nm, char *tnm, int d)
{
	char *tok, *tag;
	object *q = t->v.obj2;
	boolean show_token = 1, show_interface = 1;
	boolean show_ignore = 1, show_defn = 1;

	/* Find the token type */
	switch (t->id) EXHAUSTIVE {
	case TYPE_STRUCT : tok = "STRUCT"; tag = ""; break;
	case TYPE_STRUCT_TAG : tok = "STRUCT"; tag = "TAG "; break;
	case TYPE_UNION : tok = "UNION"; tag = ""; break;
	case TYPE_UNION_TAG : tok = "UNION"; tag = "TAG "; break;
	}

	/* Deal with undefined tokens immediately */
	if (q == null) {
		OUT (output, "#pragma token %s %s%s # %s\n", tok, tag, nm, tnm);
		return;
	}

	/* Deal with the various definition cases */
	switch (t->state) {
	case 0 : {
	    /* Definition is immediate */
	    if (is_hidden (nm)) {
			show_token = 0;
			show_interface = 0;
			show_ignore = 0;
	    }
	    break;
	}
	case 1 : {
	    /* Definition is elsewhere */
	    show_interface = 0;
	    show_ignore = 0;
	    show_defn = 0;
	    t->state = 2;
	    break;
	}
	case 2 : {
	    /* Declaration was earlier in file */
	    show_token = 0;
	    t->state = 0;
	    break;
	}
	case 3 : {
	    /* Declaration was in another file */
	    if (d) {
			show_token = 0;
			show_interface = 0;
			t->state = 1;
	    } else {
			show_interface = 0;
			show_ignore = 0;
			show_defn = 0;
			t->state = 2;
	    }
	    break;
	}
	}

	/* Print the token if necessary */
	if (show_token) {
		OUT (output, "#pragma token %s %s%s # %s\n", tok, tag, nm, tnm);
	}

	/* Print the interface statement */
	if (show_interface) {
		char *b = BUILDING_MACRO;
		OUT (output, "#ifdef %s\n", b);
		OUT (output, "#pragma interface %s%s\n", tag, nm);
		OUT (output, "#else /* %s */\n", b);
	}

	/* Print the ignore statement */
	if (show_ignore) {
		if (!show_interface) {
			char *b = BUILDING_MACRO;
			OUT (output, "#ifndef %s\n", b);
		}
		OUT (output, "#pragma ignore %s%s\n", tag, nm);
	}

	/* Print the type definition */
	if (show_defn) {
		tok = (tok [0] == 'S' ? "struct" : "union");
		if (*tag) {
			OUT (output, "%s %s {\n", tok, nm);
		} else {
			OUT (output, "typedef %s {\n", tok);
		}
		while (q) {
			field *f = q->u.u_obj->u.u_field;
			OUTS (output, "    ");
			print_type (output, f->ftype, f->fname, 0);
			OUTS (output, ";\n");
			q = q->next;
		}
		if (*tag) {
			OUTS (output, "};\n");
		} else {
			OUT (output, "} %s;\n", nm);
		}
	}

	/* Print the final #endif */
	if (show_interface || show_ignore) {
		char *b = BUILDING_MACRO;
		OUT (output, "#endif /* %s */\n", b);
	}
	return;
}


/*
 *    PRINT A TOKENISED TYPE
 *
 *    This routine is the special case of print_token which deals with
 *    tokenised types.
 */

static void
print_token_type(FILE *output, object *p, char *tnm)
{
	char *tok = "TYPE";
	char *nm = p->name;
	type *t = p->u.u_type;
	int i = t->id;
	switch (i) {

	case TYPE_DEFINED : {
	    /* Defined types */
	    char *tm, *sp;
	    type *s = t->v.next;
	    char *b = BUILDING_MACRO;
	    if (s == type_bottom) {
			sp = "bottom";
	    } else if (s == type_printf) {
			sp = "... printf";
	    } else if (s == type_scanf) {
			sp = "... scanf";
	    } else {
			OUTS (output, "typedef ");
			print_type (output, s, nm, 0);
			OUTS (output, ";\n");
			break;
	    }
	    /* Allow for special types */
	    tm = "__TenDRA__";
	    OUT (output, "#ifndef %s\n", b);
	    OUT (output, "#ifdef %s\n", tm);
	    OUT (output, "#pragma TenDRA type %s for %s\n", nm, sp);
	    OUT (output, "#else /* %s */\n", tm);
	    OUT (output, "typedef %s %s;\n", s->u.obj->name, nm);
	    OUT (output, "#endif /* %s */\n", tm);
	    OUT (output, "#else /* %s */\n", b);
	    OUT (output, "typedef %s %s;\n", s->u.obj->name, nm);
	    OUT (output, "#endif /* %s */\n", b);
	    break;
	}

	case TYPE_INT : tok = "VARIETY"; goto generic_lab;
	case TYPE_SIGNED : tok = "VARIETY signed"; goto generic_lab;
	case TYPE_UNSIGNED : tok = "VARIETY unsigned"; goto generic_lab;
	case TYPE_FLOAT : tok = "FLOAT"; goto generic_lab;
	case TYPE_ARITH : tok = "ARITHMETIC"; goto generic_lab;
	case TYPE_SCALAR : tok = "SCALAR"; goto generic_lab;

	case TYPE_GENERIC :
		generic_lab : {
			/* Generic types */
			OUT (output, "#pragma token %s %s # %s\n", tok, nm, tnm);
			break;
		}

	case TYPE_PROMOTE : {
	    /* Promotion types */
	    char *pt = t->v.next->u.obj->name;
	    OUT (output, "#pragma token VARIETY %s # %s\n", nm, tnm);
	    OUT (output, "#pragma promote %s : %s\n", pt, nm);
	    break;
	}

	case TYPE_STRUCT :
	case TYPE_STRUCT_TAG :
	case TYPE_UNION :
	case TYPE_UNION_TAG : {
	    /* Structure or union types */
	    print_struct_defn (output, t, nm, tnm, 0);
	    break;
	}

	case TYPE_ENUM :
	case TYPE_ENUM_TAG : {
	    /* Enumeration types are a complete hack */
	    char *b = BUILDING_MACRO;
	    boolean tagged = (boolean) (i == TYPE_ENUM ? 0 : 1);
	    object *q = t->v.obj2;
	    OUT (output, "#ifndef %s\n", b);

	    /* Print the enumeration type */
	    if (tagged) {
			OUT (output, "typedef enum %s {", nm);
	    } else {
			OUTS (output, "typedef enum {");
	    }

	    /* Print the enumeration elements */
	    while (q) {
			object *r = q->u.u_obj;
			char *v = r->u.u_str;
			if (v && v [0]) {
				OUT (output, "\n    %s = %s", r->name, v);
			} else {
				OUT (output, "\n    %s", r->name);
			}
			q = q->next;
			if (q) OUTC (output, ',');
	    }

	    /* Print the end of the enumeration type */
	    if (tagged) {
			IGNORE sprintf (buffer, "%s%s", enum_hack, nm);
			OUT (output, "\n} %s;\n", buffer);
	    } else {
			OUT (output, "\n} %s;\n", nm);
	    }

	    /* Print the hacked library building version */
	    OUT (output, "#else /* %s */\n", b);
	    if (tagged) {
			OUT (output, "typedef int %s;\n", buffer);
	    } else {
			OUT (output, "#pragma token VARIETY %s # %s\n", nm, tnm);
			OUT (output, "#pragma promote %s : %s\n", nm, nm);
			OUT (output, "#pragma interface %s\n", nm);
	    }
	    OUT (output, "#endif /* %s */\n", b);
	    break;
	}

	default : {
	    /* Other types */
	    MSG_unknown_type_identifier (i);
	    break;
	}
	}
	return;
}


/*
 *    PRINT A TOKEN
 *
 *    This routine prints the object p, representing the token tnm, to the
 *    file output.
 */

static void
print_token(FILE *output, object *p, char *tnm)
{
	char *nm = p->name;
	switch (p->objtype) {

	case OBJ_CONST :
	case OBJ_EXP : {
	    /* Constants and expressions */
	    type *t = p->u.u_type;
	    OUTS (output, "#pragma token EXP ");
	    if (p->objtype == OBJ_CONST && t->id == TYPE_RVALUE) {
			OUTS (output, "const : ");
			t = t->u.subtype;
	    }
	    print_type (output, t, NULL, 1);
	    OUT (output, " : %s # %s\n", nm, tnm);
	    break;
	}

	case OBJ_EXTERN : {
	    /* External expressions */
	    type *t = p->u.u_type;
	    if (t->id == TYPE_LVALUE) t = t->u.subtype;
	    OUTS (output, "extern ");
	    print_type (output, t, nm, 0);
	    OUTS (output, ";\n");
	    break;
	}

	case OBJ_WEAK : {
	    /* Weak prototype declarations */
	    int sp;
	    char *w = WEAK_PROTO;
	    type *t = p->u.u_type;
	    if (!weak_proto) {
			char *b = BUILDING_MACRO;
			OUT (output, "#ifndef %s\n", w);
			OUT (output, "#ifndef %s\n", b);
			OUT (output, "#pragma TenDRA keyword %s_KEY for weak\n", w);
			OUT (output, "#define %s(A)\t%s_KEY A\n", w, w);
			OUT (output, "#else /* %s */\n", b);
			OUT (output, "#define %s(A)\t()\n", w);
			OUT (output, "#endif /* %s */\n", b);
			OUT (output, "#endif /* %s */\n\n", w);
			weak_proto = 1;
	    }
	    OUTS (output, "extern ");
	    sp = print_head (output, t, 0, 0);
	    if (sp) OUTC (output, ' ');
	    OUT (output, "%s %s (", nm, w);
	    print_tail (output, t, 0);
	    OUTS (output, ");\n");
	    break;
	}

	case OBJ_DEFINE : {
	    /* Macro definitions */
	    char *s = p->u.u_str;
	    OUT (output, "#define %s%s\n", nm, s);
	    break;
	}

	case OBJ_FIELD : {
	    /* Field selectors */
	    field *f = p->u.u_field;
	    OUTS (output, "#pragma token MEMBER ");
	    print_type (output, f->ftype, NULL, 1);
	    OUTS (output, " : ");
	    print_type (output, f->stype, NULL, 1);
	    OUT (output, " : %s # %s\n", f->fname, tnm);
	    break;
	}

	case OBJ_FUNC : {
	    /* Functions */
	    type *t = p->u.u_type;
	    OUTS (output, "#pragma token FUNC ");
	    print_type (output, t, NULL, 1);
	    OUT (output, " : %s # %s\n", nm, tnm);
	    break;
	}

	case OBJ_MACRO : {
	    /* Macros */
	    type *t = p->u.u_type;
	    type *s = t->v.next;
	    OUTS (output, "#pragma token PROC (");
	    /* Print the macro arguments */
	    while (s && s != type_none ) {
			OUTS (output, "EXP ");
			print_type (output, s->u.subtype, NULL, 1);
			s = s->v.next;
			OUTS (output, (s ? " : , " : " : "));
	    }
	    /* Print the macro result */
	    OUTS (output, ") EXP ");
	    print_type (output, t->u.subtype, NULL, 1);
	    OUT (output, " : %s # %s\n", nm, tnm);
	    break;
	}

	case OBJ_NAT : {
	    /* Nats */
	    OUT (output, "#pragma token NAT %s # %s\n", nm, tnm);
	    break;
	}

	case OBJ_STATEMENT : {
	    /* Statements */
	    type *t = p->u.u_type;
	    if (t != null) {
			/* Statements with arguments */
			type *s = t->v.next;
			OUTS (output, "#pragma token PROC (");
			while (s && s != type_none) {
				OUTS (output, "EXP ");
				print_type (output, s->u.subtype, NULL, 1);
				s = s->v.next;
				OUTS (output, (s ? " : , " : " : "));
			}
			OUT (output, ") STATEMENT %s # %s\n", nm, tnm);
	    } else {
			/* Statements with no arguments */
			OUT (output, "#pragma token STATEMENT %s # %s\n", nm, tnm);
	    }
	    break;
	}

	case OBJ_TOKEN : {
	    /* Tokens */
	    char *s = p->u.u_str;
	    OUT (output, "#pragma token %s %s # %s\n", s, nm, tnm);
	    break;
	}

	case OBJ_TYPE : {
	    /* Types */
	    print_token_type (output, p, tnm);
	    break;
	}

	default : {
	    /* Unknown objects */
	    MSG_unknown_object_type (p->objtype);
	    break;
	}
	}
	return;
}


/*
 *    TYPE REPRESENTING AN IF STATEMENT
 *
 *    All if, else and endif statements are stored and simplified prior to
 *    output.  The ifcmd structure is used to represent the commands, with
 *    the dir field giving the command type and the nm field the associated
 *    expression.
 */

typedef struct {
	int dir;
	char *nm;
} ifcmd;


/*
 *    PRINT A NUMBER OF IF STATEMENTS
 *
 *    This routine outputs the list of if statements, ifs, to the file
 *    output.
 */

static void
print_ifs(FILE *output, ifcmd *ifs)
{
	ifcmd *p;
	boolean changed;

	/* Simplify the list of statements */
	do {
		ifcmd *q = null;
		changed = 0;
		for (p = ifs; p->dir != CMD_END; p++) {
			int d = p->dir;
			if (d != CMD_NONE) {
				if (q && q->dir != CMD_NONE) {
					int e = q->dir;
					if (d == CMD_ENDIF) {
						if (e == CMD_ELSE) {
							/* else + endif -> endif */
							q->dir = CMD_NONE;
							changed = 1;
						} else if (e != CMD_ENDIF) {
							/* if + endif -> nothing */
							p->dir = CMD_NONE;
							q->dir = CMD_NONE;
							changed = 1;
						}
					}
					if (d == CMD_ELSE) {
						if (e == CMD_IFDEF) {
							/* ifdef + else -> ifndef */
							p->dir = CMD_IFNDEF;
							q->dir = CMD_NONE;
							changed = 1;
						} else if (e == CMD_IFDEF) {
							/* ifndef + else -> ifdef */
							p->dir = CMD_IFDEF;
							q->dir = CMD_NONE;
							changed = 1;
						}
					}
				}
				q = p;
			}
		}
	} while (changed);

	/* Print the result */
	if (column) OUTC (output, '\n');
	for (p = ifs; p->dir != CMD_END; p++) {
		switch (p->dir) {
	    case CMD_IF : {
			OUT (output, "#if %s\n", p->nm);
			break;
	    }
	    case CMD_IFDEF : {
			OUT (output, "#ifdef %s\n", p->nm);
			break;
	    }
	    case CMD_IFNDEF : {
			OUT (output, "#ifndef %s\n", p->nm);
			break;
	    }
	    case CMD_ELSE : {
			OUT (output, "#else /* %s */\n", p->nm);
			break;
	    }
	    case CMD_ENDIF : {
			OUT (output, "#endif /* %s */\n", p->nm);
			break;
	    }
		}
	}
	column = 0;
	ifs [0].dir = CMD_END;
	return;
}


/*
 *    PRINT AN INTERFACE ITEM
 *
 *    This routine prints an interface statement for the object p to the
 *    file output.
 */

static void
print_interface(FILE *output, object *p, ifcmd *ifs)
{
	char *nm = p->name;
	switch (p->objtype) {

	case OBJ_CONST :
	case OBJ_EXP :
	case OBJ_MACRO :
	case OBJ_NAT :
	case OBJ_STATEMENT :
	case OBJ_TOKEN : {
	    /* Simple tokens are easy */
	    break;
	}

	case OBJ_EXTERN :
	case OBJ_WEAK : {
	    /* Deal with externals */
	    nm = null;
	    break;
	}

	case OBJ_FIELD : {
	    /* Deal with fields */
	    field *f = p->u.u_field;
	    switch (f->stype->id) {
		case TYPE_STRUCT_TAG :
		case TYPE_UNION_TAG : {
		    /* Tagged types require some attention */
		    IGNORE sprintf (buffer, "TAG %s", nm);
		    nm = buffer;
		    break;
		}
	    }
	    break;
	}

	case OBJ_FUNC : {
	    /* Functions containing ... are not actually tokens */
	    type *t = p->u.u_type->v.next;
	    while (t != null) {
			if (t->u.subtype == type_ellipsis) {
				nm = null;
				break;
			}
			t = t->v.next;
	    }
	    break;
	}

	case OBJ_DEFINE : {
	    /* Macro definitions are not tokens */
	    nm = null;
	    break;
	}

	case OBJ_TYPE : {
	    /* Deal with types */
	    type *t = p->u.u_type;
	    switch (t->id) {
		case TYPE_STRUCT_TAG :
		case TYPE_UNION_TAG : {
		    /* Tagged types require some attention */
		    IGNORE sprintf (buffer, "TAG %s", nm);
		    nm = buffer;
		    goto type_struct_lab;
		}
		case TYPE_STRUCT :
		case TYPE_UNION :
			type_struct_lab : {
				/* Some structures and unions are not tokens */
				if (t->v.obj2) {
					if (t->state == 2) {
						t->state = 3;
					} else {
						nm = null;
					}
				}
				break;
			}
		case TYPE_DEFINED : {
		    /* Type definitions are not tokens */
		    nm = null;
		    break;
		}
		case TYPE_ENUM :
		case TYPE_ENUM_TAG : {
		    /* Enumeration types are not tokens */
		    nm = null;
		    break;
		}
	    }
	    break;
	}

	default : {
	    /* Unknown objects */
	    MSG_unknown_object_type (p->objtype);
	    nm = null;
	    break;
	}
	}

	/* Print the interface statement */
	if (nm) {
		int n = (int) strlen (nm) + 1;
		if (ifs [0].dir != CMD_END) print_ifs (output, ifs);
		if (column + n >= 60) {
			OUTC (output, '\n');
			column = 0;
		}
		if (column == 0) OUTS (output, "#pragma interface");
		OUTC (output, ' ');
		OUTS (output, nm);
		column += n;
	}
	return;
}


/*
 *    PRINT AN INCLUDED FILE
 */

static void
print_include(FILE *output, char *nm, int on)
{
	object *p;
	if (nm == null) return;
	IGNORE sprintf (buffer, "%s[%s]", crt_info->src, nm);
	if (search_hash (files, buffer, no_version)) return;
	p = make_object (string_copy (buffer), OBJ_FILE);
	p->u.u_file = null;
	IGNORE add_hash (files, p, no_version);
	if (on) OUT (output, "#include <%s>\n", nm);
	return;
}


/*
 *    PRINT AN OBJECT
 *
 *    This routine prints the list of objects input to the file output, the
 *    form of the information to be printed being indicated by pass.
 */

static void
print_object(FILE *output, object *input, int pass)
{
	object *p;
	ifcmd ifs [100];
	ifs [0].dir = CMD_END;
	for (p = input; p != null; p = p->next) {
		char *nm = p->name;
		switch (p->objtype) {

	    case OBJ_IF	 : {
			/* If statements etc. */
			if (pass != 1) {
				int i = 0;
				while (ifs [i].dir != CMD_END) i++;
				ifs [i].dir = p->u.u_num;
				ifs [i].nm = p->name;
				ifs [i + 1].dir = CMD_END;
				if (i >= 90) print_ifs (output, ifs);
			}
			break;
	    }

	    case OBJ_IMPLEMENT :
	    case OBJ_USE : {
			/* Inclusion statements */
			if (pass < 2 && nm [pass] == '1') {
				object *q = p->u.u_obj;
				info *i = q->u.u_info;
				char *b = BUILDING_MACRO;
				if (streq (i->api, LOCAL_API)) break;
				if (ifs [0].dir != CMD_END) print_ifs (output, ifs);
				if (pass == 0) {
					char *f;
					char *dir;
					char *m = i->protect;
					int n = output_incl_len;
					if (nm [2] == 'G') {
						OUT (output, "#ifndef %s\n", b);
						dir = "#pragma extend interface [%s]\n";
						OUT (output, dir, i->file);
						OUT (output, "#else /* %s */\n", b);
						m = "";
					}
					if (*m) OUT (output, "#ifndef %s\n", m);
					if (local_input) {
						f = i->incl + n;
						dir = "#pragma extend interface <../%s>\n";
					} else {
						f = relative (crt_info->incl, i->incl, n);
						dir = "#pragma extend interface \"%s\"\n";
					}
					OUT (output, dir, f);
					if (*m) OUT (output, "#endif /* %s */\n", m);
					if (nm [2] == 'G') {
						OUT (output, "#endif /* %s */\n", b);
					}
				} else {
					print_include (output, i->file, 1);
				}
			}
			break;
	    }

	    case OBJ_SET : {
			/* Subsets */
			object *q = p->u.u_obj;
			info *i = q->u.u_info;
			if (streq (i->api, LOCAL_API)) {
				if (ifs [0].dir != CMD_END) print_ifs (output, ifs);
				print_object (output, i->elements, pass);
			} else {
				if (pass < 2) {
					if (ifs [0].dir != CMD_END) {
						print_ifs (output, ifs);
					}
					print_set (p, pass);
				}
			}
			break;
	    }

	    case OBJ_TEXT_INCL : {
			/* Include file quoted text */
			if (pass == 0) {
				if (ifs [0].dir != CMD_END) print_ifs (output, ifs);
				OUTS (output, nm);
				OUTC (output, '\n');
			}
			break;
	    }

	    case OBJ_TEXT_SRC : {
			/* Source file quoted text */
			if (pass == 1) {
				if (ifs [0].dir != CMD_END) print_ifs (output, ifs);
				OUTS (output, nm);
				OUTC (output, '\n');
			}
			break;
	    }

	    case OBJ_TOKEN : {
			/* Tokenised objects */
			if (pass == 0) {
				if (ifs [0].dir != CMD_END) print_ifs (output, ifs);
				print_token (output, p->u.u_obj, nm);
			} else if (pass == 2) {
				print_interface (output, p->u.u_obj, ifs);
			}
			break;
	    }

	    case OBJ_TYPE : {
			/* Definition of previously declared type */
			if (pass == 0) {
				type *t = p->u.u_type;
				char *tnm = t->u.obj->name;
				print_struct_defn (output, t, tnm, tnm, 1);
			}
			break;
	    }

	    default : {
			/* Unknown objects */
			MSG_unknown_object_type (p->objtype);
			break;
	    }
		}
	}
	if (ifs [0].dir != CMD_END) print_ifs (output, ifs);
	return;
}


/*
 *    SCAN AN OBJECT
 *
 *    This routine scans the object input, calling print_set on any subsets.
 */

static void
scan_object(object *input, int pass)
{
	object *p;
	for (p = input; p != null; p = p->next) {
		if (p->objtype == OBJ_SET) {
			object *q = p->u.u_obj;
			info *i = q->u.u_info;
			if (streq (i->api, LOCAL_API)) {
				scan_object (i->elements, pass);
			} else {
				if (pass < 2) print_set (p, pass);
			}
		}
	}
	return;
}


/*
 *    PRINT A SET
 *
 *    This routine prints the set of objects given by input.  The form of the
 *    output is indicated by pass.
 */

void
print_set(object *input, int pass)
{
	char *nm;
	time_t t1, t2;
	FILE *output = null;
	object *ss = input->u.u_obj;
	info *i = ss->u.u_info;
	column = 0;

	if (streq (i->api, LOCAL_API)) {
		/* Local files go to the standard output */
		if (pass != 0) return;
		nm = "stdout";
		output = stdout;
		t1 = (time_t) 0;
		t2 = (time_t) 0;
	} else {
		nm = (pass ? i->src : i->incl);
		if (nm == null || (restrict_use && i->implemented == 0)) {
			scan_object (i->elements, 1);
			return;
		}
		if (pass == 1 && i->tokens == 0) {
			if (verbose > 1) {
				IGNORE printf ("%s is not required ...\n", nm);
			}
			scan_object (i->elements, 1);
			return;
		}
		t1 = i->age;
		if (progdate > t1) t1 = progdate;
		t2 = date_stamp (nm);
	}

	if ((t1 && t1 < t2) && !force_output) {
		/* Output file is up to date */
		object *q;
		if (verbose > 1)
			IGNORE printf ("%s is up to date ...\n", nm);
		q = make_object (nm, OBJ_FILE);
		q->u.u_file = null;
		IGNORE add_hash (files, q, no_version);
		for (q = i->elements; q != null; q = q->next) {
			if (q->objtype == OBJ_SET) print_set (q, pass);
		}
	} else {
		/* Output file needs updating */
		object *q = null;
		info *old_info = crt_info;
		int old_column = column;
		boolean old_weak_proto = weak_proto;
		weak_proto = 0;

		/* Open output file */
		if (output == null) {
			create_dir (nm);
			if (verbose)
				IGNORE printf ("Creating %s ...\n", nm);
			check_name (nm);
			q = make_object (nm, OBJ_FILE);
			q->u.u_file = null;
			IGNORE add_hash (files, q, no_version);
			output = fopen (nm, "w");
			q->u.u_file = output;
			if (output == null) {
				MSG_cant_open_output_file (nm);
				return;
			}
		}

		crt_info = i;
		if (pass == 0) {
			/* Include output file */
			char *m = i->protect;
			char *v = i->version;

			/* Find the version number */
			if (v == null && i->subset) {
				char *a = subset_name (i->api, i->file, NULL);
				object *ap = make_subset (a);
				v = ap->u.u_info->version;
			}
			if (v == null && i->file) {
				char *a = subset_name (i->api, NULL, NULL);
				object *ap = make_subset (a);
				v = ap->u.u_info->version;
			}

			/* Print the file header */
			OUTS (output, "/*\n    AUTOMATICALLY GENERATED BY ");
			OUT (output, "%s %s\n", progname, progvers);
			OUT (output, "    API SUBSET: %s", ss->name);
			if (v) OUT (output, " (VERSION %s)", v);
			OUTS (output, "\n*/\n\n");

			/* Print the file body */
			if (*m) {
				OUT (output, "#ifndef %s\n", m);
				OUT (output, "#define %s\n\n", m);
			}
			if (i->elements) {
				boolean is_cpplus = 0;
				if (i->linkage) {
					if (streq (i->linkage, "C++")) {
						OUT (output, "extern \"%s\" {\n\n", i->linkage);
						is_cpplus = 1;
					} else {
						OUT (output, "#ifdef __cplusplus\n");
						OUT (output, "extern \"%s\" {\n", i->linkage);
						OUT (output, "#endif\n\n");
					}
				}
				if (i->nspace) {
					if (is_cpplus) {
						OUT (output, "namespace %s {\n\n", i->nspace);
					} else {
						OUT (output, "#ifdef __cplusplus\n");
						OUT (output, "namespace %s {\n", i->nspace);
						OUT (output, "#endif\n\n");
					}
				}
				if (i->block) {
					char *dir;
					dir = "#pragma TenDRA declaration block %s begin\n\n";
					OUT (output, dir, i->block);
				}
				print_object (output, i->elements, 0);
				if (i->tokens) OUTC (output, '\n');
				print_object (output, i->elements, 2);
				if (column) OUTC (output, '\n');
				if (i->block) {
					char *dir;
					dir = "\n#pragma TenDRA declaration block end\n";
					OUT (output, dir);
				}
				if (i->nspace) {
					if (is_cpplus) {
						OUT (output, "\n}\n");
					} else {
						OUT (output, "\n#ifdef __cplusplus\n");
						OUT (output, "}\n");
						OUT (output, "#endif\n");
					}
				}
				if (i->linkage) {
					if (is_cpplus) {
						OUT (output, "\n}\n");
					} else {
						OUT (output, "\n#ifdef __cplusplus\n");
						OUT (output, "}\n");
						OUT (output, "#endif\n");
					}
				}
			}
			if (*m) OUT (output, "\n#endif /* %s */\n", m);

		} else {
			/* Source output file */
			if (i->method == null) {
				char *m, *s;
				char *w1, *w2;
				int n = output_incl_len;
				m = macro_name (DEFINE_PREFIX, i->api, i->file, i->subset);
				w1 = macro_name (WRONG_PREFIX, i->api, NULL, NULL);
				w2 = macro_name (WRONG_PREFIX, i->api, i->file, i->subset);
				s = i->incl + n;
				OUTS (output, "/* AUTOMATICALLY GENERATED BY ");
				OUT (output, "%s %s */\n", progname, progvers);
				OUT (output, "#ifndef %s\n", w1);
				OUT (output, "#ifndef %s\n", w2);
				OUT (output, "#if #include (%s)\n", i->file);
				OUT (output, "#define %s\n", m);
				print_include (output, i->file, 0);
				print_object (output, i->elements, 1);
				OUT (output, "#include <%s>\n", i->file);
				OUT (output, "#endif\n");
				OUT (output, "#endif\n\n");
				OUT (output, "#ifndef %s\n", m);
				OUT (output, "#pragma TenDRA no token definition allow\n");
				OUT (output, "#endif\n");
				OUT (output, "#pragma implement interface <../%s>\n", s);
				OUT (output, "#endif\n");
			} else {
				print_object (output, i->elements, 1);
			}
		}

		/* End the output */
		IGNORE fclose (output);
		if (q) q->u.u_file = null;
		crt_info = old_info;
		column = old_column;
		weak_proto = old_weak_proto;
	}
	return;
}
