/*
 * 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/tnc/write.c,v 1.9 2005/09/21 21:18:35 stefanf Exp $
 */


#include "config.h"
#include <ctype.h>
#include "types.h"
#include "file.h"
#include "high.h"
#include "names.h"
#include "node.h"
#include "shape.h"
#include "table.h"
#include "tdf.h"
#include "utility.h"
#include "write.h"


/*
 *    FLAGS CONTROLLING OUTPUT OF TOKENS ETC
 *
 *    The output is in the fully expanded form if verbose is true.  The
 *    shape of each expression is printed if print_shapes is true.  The
 *    flag func_output controls whether the output should be lisp-like
 *    (default) or c-like.
 */

BoolT verbose = 0;
boolean print_shapes = 0;
BoolT func_output = 0;


/*
 *    PRINT A NUMBER OF SPACES
 *
 *    An indentation of d spaces is printed to the output file.
 */

static void
print_spaces(int d)
{
    int n = 2 * d;
    while (n >= 8) {
		IGNORE fputc ('\t', output);
		n -= 8;
    }
    while (n) {
		IGNORE fputc (' ', output);
		n--;
    }
    return;
}


/*
 *    PRINT A NODE
 *
 *    The node p is printed to the output file with an indentation of
 *    d spaces.
 */

static boolean
print_node(node *p, int d)
{
    boolean negate = 0;
    boolean newline = 0;
    while (p) {
		construct *q = p->cons;
		sortname s = q->sortnum;
		long m = q->encoding;
		newline = 0;
		switch (s) {

	    case SORT_tdfbool : {
			/* Set neg for subsequent number */
			negate = (boolean) (m ? 1 : 0);
			break;
	    }

	    case SORT_bytestream : {
			/* Print a bytestream */
			newline = print_node (p->son, d);
			break;
	    }

	    case SORT_completion : {
			/* Print a completion */
			newline = print_node (p->son, d);
			break;
	    }

	    case SORT_small_tdfint : {
			/* Print a small number */
			long n = q->encoding;
			print_spaces (d);
			if (negate) n = -n;
			IGNORE fprintf (output, "%ld", n);
			negate = 0;
			newline = 1;
			break;
	    }

	    case SORT_tdfint : {
			/* Print a number */
			char *num = q->name;
			print_spaces (d);
			if (fits_ulong (num, 0)) {
				unsigned long n = octal_to_ulong (num);
				if (negate && n) IGNORE fputc ('-', output);
				IGNORE fprintf (output, "%lu", n);
			} else {
				if (negate) IGNORE fputc ('-', output);
				IGNORE fprintf (output, "0%s", num);
			}
			negate = 0;
			newline = 1;
			break;
	    }

	    case SORT_option : {
			/* Print an optional argument */
			if (p->son) {
				newline = print_node (p->son, d);
			} else {
				print_spaces (d);
				IGNORE fputc ('-', output);
				newline = 1;
			}
			break;
	    }

	    case SORT_repeat : {
			/* Print a repeated argument */
			if (m == 0) {
				print_spaces (d);
				IGNORE fputc ('|', output);
				newline = 1;
			} else {
				newline = print_node (p->son, d);
				if (func_output) IGNORE fputc (',', output);
				IGNORE fputs (" |", output);
			}
			break;
	    }

	    case SORT_tdfstring : {
			/* Print a string */
			int i, n = (int) m;
			print_spaces (d);
			if (n == -1) {
				char *f = (func_output ? "%s (\n" : "(%s\n");
				IGNORE fprintf (output, f, MAKE_STRING);
				newline = print_node (p->son, d + 1);
				IGNORE fputs (")", output);
			} else {
				IGNORE fputc ('"', output);
				for (i = 0 ; i < n ; i++) {
					int c = ((q->name [i]) & 0xff);
					if (isprint (c)) {
						if (c == '\\' || c == '"') {
							IGNORE fputc ('\\', output);
						}
						IGNORE fputc (c, output);
					} else {
						if (c == '\n') {
							IGNORE fputs ("\\n", output);
						} else if (c == '\t') {
							IGNORE fputs ("\\t", output);
						} else {
							unsigned co = (unsigned) c;
							IGNORE fprintf (output, "\\%03o", co);
						}
					}
				}
				IGNORE fputc ('"', output);
				newline = 1;
			}
			break;
	    }

	    case SORT_nat : {
			/* Print a nat */
			if (m != ENC_make_nat) goto default_label;
			newline = print_node (p->son, d);
			break;
	    }

	    case SORT_signed_nat : {
			/* Print a signed_nat */
			if (m != ENC_make_signed_nat) goto default_label;
			newline = print_node (p->son, d);
			break;
	    }

	    case SORT_string : {
			/* Print a string */
			if (m != ENC_make_string) goto default_label;
			newline = print_node (p->son, d);
			break;
	    }

	    case SORT_al_tag : {
			/* Print an alignment tag */
			if (verbose || m != ENC_make_al_tag || p->son == null) {
				goto default_label;
			}
			newline = print_node (p->son, d);
			break;
	    }

	    case SORT_label : {
			/* Print a label */
			if (verbose || m != ENC_make_label || p->son == null) {
				goto default_label;
			}
			newline = print_node (p->son, d);
			break;
	    }

	    case SORT_tag : {
			/* Print a tag */
			if (verbose || m != ENC_make_tag || p->son == null) {
				goto default_label;
			}
			newline = print_node (p->son, d);
			break;
	    }

	    case SORT_exp : {
			static node special_node;
			if (print_shapes && p->shape != &special_node) {
				/* Change exp to show shape */
				node *z = new_node ();
				z->cons = &exp_shape;
				z->bro = p->bro;
				z->son = p;
				if (p->shape) {
					z->son->bro = copy_node (p->shape);
				} else {
					z->son->bro = new_node ();
					z->son->bro->cons = &unknown_cons;
				}
				p->shape = &special_node;
				p = z;
				q = p->cons;
				m = q->encoding;
			}
			goto default_label;
	    }

	    default : {
			/* Print a simple sort */
			default_label : {
				if (!verbose && m == sort_tokens [s] && p->son &&
					p->son->cons->sortnum == SORT_token) {
					newline = print_node (p->son, d);
				} else {
					print_spaces (d);
					if (p->son) {
						char *f = (func_output ? "%s (\n" : "(%s\n");
						IGNORE fprintf (output, f, q->name);
						newline = print_node (p->son, d + 1);
						IGNORE fputs (")", output);
					} else {
						IGNORE fprintf (output, "%s", q->name);
						newline = 1;
					}
				}
			}
			break;
	    }
		}
		p = p->bro;
		if (newline && p) {
			if (func_output) IGNORE fputc (',', output);
			IGNORE fputc ('\n', output);
			newline = 0;
		}
    }
    return (newline);
}


/*
 *    PRINT AN EXTERNAL NAME
 *
 *    The start of a statement with name title concerning the construct
 *    p is output.  dec is true if this is the first statement concerning p.
 */

static void
print_name(char *title, construct *p, int dec)
{
    if (!func_output) IGNORE fputs ("(", output);
    if (p->ename == null) IGNORE fprintf (output, "%s ", LOCAL_DECL);
    IGNORE fprintf (output, "%s", title);
    if (func_output) IGNORE fputs (" (", output);
    if (p->ename && p->ename->cons->encoding && dec) {
		char *f = (func_output ? "\n  %s (\n" : "\n  (%s\n");
		if (p->ename->son->cons->sortnum == SORT_tdfstring) {
			if (p->ename->son->bro == null) {
				IGNORE fprintf (output, f, MAKE_STRING_EXTERN);
			} else {
				IGNORE fprintf (output, f, MAKE_CHAIN_EXTERN);
			}
		} else {
			IGNORE fprintf (output, f, MAKE_UNIQUE_EXTERN);
		}
		IGNORE print_node (p->ename->son, 2);
		if (func_output) {
			IGNORE fputs ("),\n  ", output);
		} else {
			IGNORE fputs (")\n  ", output);
		}
    } else {
		IGNORE fputc (' ', output);
    }
    IGNORE fprintf (output, "%s", p->name);
    return;
}


/*
 *    ALIGNMENT TAG DECLARATION AUXILIARY PRINTING ROUTINE
 *
 *    Print the declaration of the alignment tag p.
 */

static void
print_aldec(construct *p)
{
    if (p->encoding == -1) return;
    print_name (MAKE_ALDEC, p, 1);
    IGNORE fputs ((func_output ? ") ;\n\n" : ")\n\n"), output);
    return;
}


/*
 *    ALIGNMENT TAG DEFINITION AUXILIARY PRINTING ROUTINE
 *
 *    Print the definition of the alignment tag p.
 */

static void
print_aldef(construct *p)
{
    al_tag_info *info = get_al_tag_info (p);
    if (p->encoding == -1) return;
    if (info->def == null) return;
    print_name (MAKE_ALDEF, p, !show_aldecs);
    if (func_output) IGNORE fputc (',', output);
    IGNORE fputc ('\n', output);
    IGNORE print_node (info->def, 1);
    IGNORE fputs ((func_output ? ") ;\n\n" : ")\n\n"), output);
    return;
}


/*
 *    TAG DECLARATION AUXILIARY PRINTING ROUTINE
 *
 *    Print the declaration of the tag p.
 */

static void
print_tagdec(construct *p)
{
    tag_info *info = get_tag_info (p);
    if (p->encoding == -1 || info->dec == null) return;
    switch (info->var) {
	case 0 : print_name (MAKE_ID_TAGDEC, p, 1) ; break;
	case 1 : print_name (MAKE_VAR_TAGDEC, p, 1) ; break;
	case 2 : print_name (COMMON_TAGDEC, p, 1) ; break;
    }
    if (func_output) IGNORE fputc (',', output);
    IGNORE fputc ('\n', output);
    IGNORE print_node (info->dec, 1);
    IGNORE fputs ((func_output ? ") ;\n\n" : ")\n\n"), output);
    return;
}


/*
 *    TAG DEFINITION AUXILIARY PRINTING ROUTINE
 *
 *    Print the definition of the tag p.
 */

static void
print_tagdef(construct *p)
{
    char *instr;
    tag_info *info = get_tag_info (p);
    node *d = info->def;
    if (p->encoding == -1 || d == null) return;
    switch (info->var) EXHAUSTIVE {
	case 0 : instr = MAKE_ID_TAGDEF ; break;
	case 1 : instr = MAKE_VAR_TAGDEF ; break;
	case 2 : instr = COMMON_TAGDEF ; break;
	default: instr = "UNKNOWN TAGDEF";
    }
    while (d) {
		/* Can have multiple definitions */
		print_name (instr, p, !show_tagdecs);
		if (func_output) IGNORE fputc (',', output);
		IGNORE fputc ('\n', output);
		IGNORE print_node (d->son, 1);
		IGNORE fputs ((func_output ? ") ;\n\n" : ")\n\n"), output);
		d = d->bro;
    }
    return;
}


/*
 *    TOKEN DECLARATION AUXILIARY PRINTING ROUTINE
 *
 *    Print the declaration of the token p.
 */

static void
print_tokdec(construct *p)
{
    tok_info *info = get_tok_info (p);
    if (p->encoding == -1) return;
    if (!info->dec) return;
    print_name (MAKE_TOKDEC, p, 1);
    if (func_output) IGNORE fputc (',', output);
    IGNORE fputc ('\n', output);
    if (info->sig) {
		IGNORE print_node (info->sig, 1);
    } else {
		print_spaces (1);
		IGNORE fputc ('-', output);
    }
    if (func_output) IGNORE fputc (',', output);
    IGNORE fputs ("\n  ", output);
    if (info->args) {
		int n = 0;
		char *q = info->args;
		IGNORE fputs ("(", output);
		while (*q) {
			sortname s;
			q = find_sortname (q, &s);
			q++;
			if (n++ == 8) {
				IGNORE fputs ("\n  ", output);
				n = 1;
			}
			IGNORE fputs (sort_name (s), output);
			if (func_output && *q) IGNORE fputc (',', output);
			IGNORE fputc (' ', output);
		}
		IGNORE fputs (") ", output);
		if (func_output) IGNORE fputs ("-> ", output);
    }
    IGNORE fputs (sort_name (info->res), output);
    IGNORE fputs ((func_output ? ") ;\n\n" : ")\n\n"), output);
    return;
}


/*
 *    TOKEN DEFINITION AUXILIARY PRINTING ROUTINE
 *
 *    Print the definition of the token p.
 */

static void
print_tokdef(construct *p)
{
    tok_info *info = get_tok_info (p);
    if (p->encoding == -1) return;
    if (!info->dec) return;
    if (info->def == null) return;
    print_name (MAKE_TOKDEF, p, !show_tagdefs);
    if (func_output) IGNORE fputc (',', output);
    IGNORE fputc ('\n', output);
    if (info->sig) {
		IGNORE print_node (info->sig, 1);
    } else {
		print_spaces (1);
		IGNORE fputc ('-', output);
    }
    if (func_output) IGNORE fputc (',', output);
    IGNORE fputs ("\n  ", output);
    if (info->args) {
		int n = 0;
		construct **q = info->pars;
		IGNORE fputs ("(", output);
		while (*q) {
			tok_info *qinfo = get_tok_info (*q);
			if (n++ == 4) {
				IGNORE fputs ("\n    ", output);
				n = 1;
			}
			IGNORE fprintf (output, "%s %s", sort_name (qinfo->res),
							(*q)->name);
			q++;
			if (func_output && *q) IGNORE fputc (',', output);
			IGNORE fputc (' ', output);
		}
		IGNORE fputs (") ", output);
		if (func_output) IGNORE fputs ("-> ", output);
    }
    IGNORE fputs (sort_name (info->res), output);
    if (func_output) IGNORE fputc (',', output);
    IGNORE fputc ('\n', output);
    IGNORE print_node (info->def, 1);
    IGNORE fputs ((func_output ? ") ;\n\n" : ")\n\n"), output);
    return;
}


/*
 *    PRINT A HIGH-LEVEL SORT
 *
 *    This routine prints the high level sort h.
 */

static void
print_high_sort(high_sort *h)
{
    int i, n;
    if (h->id == SORT_unknown) return;
    if (func_output) {
		IGNORE fprintf (output, "%s (%s, ", MAKE_SORT, h->name);
    } else {
		IGNORE fprintf (output, "(%s %s ", MAKE_SORT, h->name);
    }
    n = h->no_args;
    if (n) {
		int m = 0;
		IGNORE fputs ("(", output);
		for (i = 0 ; i < n ; i++) {
			if (m++ == 8) {
				IGNORE fputs ("\n  ", output);
				m = 1;
			}
			IGNORE fputs (sort_name (h->args [i]), output);
			if (func_output && i < n - 1) IGNORE fputc (',', output);
			IGNORE fputc (' ', output);
		}
		IGNORE fputs (") ", output);
		if (func_output) IGNORE fputs ("-> ", output);
    }
    IGNORE fputs (sort_name (h->res), output);
    IGNORE fputs ((func_output ? ") ;\n\n" : ")\n\n"), output);
    return;
}


/*
 *    MAIN PRINTING ROUTINE
 *
 *    This routine prints an entire capsule to the output file.
 */

void
print_capsule(void)
{
    if (high_sorts) {
		int i;
		IGNORE fputs ("# HIGH-LEVEL SORTS\n\n", output);
		for (i = 0 ; i < crt_high_sort ; i++) {
			print_high_sort (high_sorts + i);
		}
		IGNORE fputc ('\n', output);
    }
    if (show_tokdecs) {
		IGNORE fputs ("# TOKEN DECLARATIONS\n\n", output);
		apply_to_all (print_tokdec, SORT_token);
		IGNORE fputc ('\n', output);
    }
    if (show_aldecs) {
		IGNORE fputs ("# ALIGNMENT TAG DECLARATIONS\n\n", output);
		apply_to_all (print_aldec, SORT_al_tag);
		IGNORE fputc ('\n', output);
    }
    if (show_tagdecs) {
		IGNORE fputs ("# TAG DECLARATIONS\n\n", output);
		apply_to_all (print_tagdec, SORT_tag);
		IGNORE fputc ('\n', output);
    }
    if (show_tokdefs) {
		IGNORE fputs ("# TOKEN DEFINITIONS\n\n", output);
		apply_to_all (print_tokdef, SORT_token);
		IGNORE fputc ('\n', output);
    }
    if (show_aldefs) {
		IGNORE fputs ("# ALIGNMENT TAG DEFINITIONS\n\n", output);
		apply_to_all (print_aldef, SORT_al_tag);
		IGNORE fputc ('\n', output);
    }
    if (show_tagdefs) {
		IGNORE fputs ("# TAG DEFINITIONS\n\n", output);
		apply_to_all (print_tagdef, SORT_tag);
		IGNORE fputc ('\n', output);
    }
    return;
}
