/*
 *                     OpenBIOS - free your system! 
 *                            ( detokenizer )
 *                          
 *  decode.c - contains output wrappers for fcode words.  
 *  
 *  This program is part of a free implementation of the IEEE 1275-1994 
 *  Standard for Boot (Initialization Configuration) Firmware.
 *
 *  Copyright (C) 2001-2005 Stefan Reinauer, <stepan@openbios.org>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; version 2 of the License.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA, 02110-1301 USA
 *
 */

/* **************************************************************************
 *         Modifications made in 2005 by IBM Corporation
 *      (C) Copyright 2005 IBM Corporation.  All Rights Reserved.
 *      Modifications Author:  David L. Paktor    dlpaktor@us.ibm.com
 **************************************************************************** */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <setjmp.h>

#include "detok.h"
#include "stream.h"
#include "addfcodes.h"

static int indent;		/*  Current level of indentation   */

/* **************************************************************************
 *
 *      Still to be done:
 *          Handling of indent-level is not correct.  Branches should
 *              balance with their resolvers; constructs like do..loop
 *              case/of/endof/endcase are a few major examples.
 *          This will be tricky; the rules need to be carefully thought
 *              out, and the implementation might be more complex than
 *              at first meets the eye...
 *
 **************************************************************************** */

static bool ended_okay = TRUE;	/*  FALSE if finished prematurely  */

bool offs16 = TRUE;
unsigned int linenum;
bool end_found = FALSE;
unsigned int token_streampos;	/*  Streampos() of currently-gotten token  */
u16 last_defined_token = 0;

jmp_buf eof_exception;

static int fclen;
static const char *unnamed = "(unnamed-fcode)";

static void decode_indent(void)
{
	int i;
	if (indent < 0) {
#ifdef DEBUG_INDENT
		printf("detok: error in indentation code.\n");
#endif
		indent = 0;
	}
	for (i = 0; i < indent; i++)
		printf("    ");
}

/*  Print forth string ( [len] char[0] ... char[len] ) */
static void pretty_print_string(void)
{
	u8 len;
	u8 *strptr;
	int indx;
	bool in_parens = FALSE;	/*  Are we already inside parentheses?  */

	strptr = get_string(&len);

	printf("( len=%s%x", len >= 10 ? "0x" : "", len);
	if (len >= 10)
		printf(" [%d bytes]", len);
	printf(" )\n");
	if (show_linenumbers)
		printf("        ");
	decode_indent();
	printf("\" ");

	for (indx = 0; indx < len; indx++) {
		u8 c = *strptr++;
		if (isprint(c)) {
			if (in_parens) {
				printf(" )");
				in_parens = FALSE;
			}
			printf("%c", c);
			/*  Quote-mark must escape itself  */
			if (c == '"')
				printf("%c", c);
		} else {
			if (!in_parens) {
				printf("\"(");
				in_parens = TRUE;
			}
			printf(" %02x", c);
		}
	}
	if (in_parens)
		printf(" )");
	printf("\"");
}

static void decode_lines(void)
{
	if (show_linenumbers) {
		printf("%6d: ",show_offsets ? token_streampos : linenum++);
	}
}

/* **************************************************************************
 *
 *      Function name:  output_token_name
 *      Synopsis:       Print the name of the token just retrieved
 *                          along with any interesting related information...
 *
 *      Inputs:
 *         Global/Static Variables:
 *             fcode                  The token # just retrieved
 *             last_defined_token     Used to screen invalid tokens.
 *             token_streampos        Location of token just retrieved
 *         Global/Static Constants:
 *             unnamed                Namefield of headerless token 
 *
 *      Outputs:
 *         Printout:
 *             Print the function name (if known) and the FCode number,
 *                  if interesting.
 *             The fcode number is interesting if either
 *                 a) the token has no name
 *                           or
 *                 b) verbose mode is in effect.
 *             If the token is named, show its FCode number in
 *                 the syntax of a FORTH Comment, otherwise, its
 *                 FCode number -- in [brackets] -- acts as its name.
 *
 *      Error Detection:
 *          If the token # is larger than the last defined token, it is
 *              probably an artifact of an error that was allowed in the
 *              tokenization; if it were treated normally, it would lead
 *              to a cascade of failures.  Print a message, skip the first
 *              byte, and return.
 *
 *      Revision History:
 *          Refactored.  The line number or offset (if selected) and the
 *              indent are printed in the  output_token()  routine, which
 *              calls this one.
 *          Add Error Detection
 *          
 *
 **************************************************************************** */

static void output_token_name(void)
{
	char *tname;

	/*  Run error detection only if last_defined_token was assigned  */
	if ((fcode > last_defined_token) && (last_defined_token > 0)) {
		char temp_buf[80];
		int buf_pos;
		u8 top_byte = fcode >> 8;
		printf("Invalid token:  [0x%03x]\n", fcode);
		sprintf(temp_buf, "Backing up over first byte, which is ");
		buf_pos = strlen(temp_buf);
		if (top_byte < 10) {
			sprintf(&temp_buf[buf_pos], " %02x", top_byte);
		} else {
			sprintf(&temp_buf[buf_pos], "0x%02x ( =dec %d)",
				top_byte, top_byte);
		}
		printremark(temp_buf);
		set_streampos(token_streampos + 1);
		return;
	}


	tname = lookup_token(fcode);
	printf("%s ", tname);

	/* The fcode number is interesting
	 *  if either
	 *  a) the token has no name
	 *            or
	 *  b) detok is in verbose mode.
	 */
	if (strcmp(tname, unnamed) == 0) {
		printf("[0x%03x] ", fcode);
	} else {
		if (verbose) {
			/*  If the token is named,
			 *  show its fcode number in
			 *  the syntax of a FORTH Comment
			 */
			printf("( 0x%03x ) ", fcode);
		}
	}
}

/* **************************************************************************
 *
 *      Function name:  output_token
 *      Synopsis:       Print the full line for the token just retrieved:
 *                          line number, indentation and name.
 *
 *      Revision History:
 *          Updated Mon, 11 Jul 2005 by David L. Paktor
 *          Separate out  output_token_name()  for specific uses.
 *          Incorporate calls to  decode_lines()  and   decode_indent()
 *              to allow better control of indentation.
 *
 **************************************************************************** */

static void output_token(void)
{
	decode_lines();
	decode_indent();
	output_token_name();
}


/* **************************************************************************
 *
 *      Function name:  decode_offset
 *      Synopsis:       Gather and display an FCode-offset associated with
 *                          a branch or suchlike function.
 *
 *      Inputs:
 *         Parameters:                 NONE
 *         Global/Static Variables:
 *             show_offsets            Whether to show offsets in full detail
 *             offs16                  Whether 16- (or 8-) -bit offsets
 *             stream_max              Maximum valid destimation      
 *         Local Static Variables:
 *
 *      Outputs:
 *         Returned Value:             The offset, converted to signed 16-bit #
 *         Global/Static Variables:
 *             stream-position         Reset if invalid destination; otherwise,
 *                                         advanced in the normal manner.
 *         Printout:
 *             The offset and destination, if display of offsets was specified
 *                 or if the destination is invalid
 *
 *      Error Detection:
 *          Crude and rudimentary:
 *          If the target-destination is outside the theoretical limits,
 *              it's obviously wrong.
 *          Notification remark is printed and the stream-position reset
 *              to the location of the offset, to allow it to be processed
 *              in the manner of normal tokens.
 *          If the offset is zero, that's obviously wrong, but don't reset
 *              the stream-position:  zero gets processed as  end0  and that
 *              is also wrong...
 *
 *      Still to be done:
 *          More sophisticated error-checking for invalid offsets.  Look
 *              at the type of branch, and what should be expected in the
 *              vicinity of the destination.  (This might be best served
 *              by a separate routine).
 *         This might also help with handling the indent-level correctly...
 *              Also, if indentation were to be handled in this routine,
 *              there would be no need to return the value of the offset.
 *
 **************************************************************************** */

static s16 decode_offset(void)
{
	s16 offs;
	int dest;
	bool invalid_dest;
	int streampos = get_streampos();

	output_token();
	offs = get_offset();

	/*  The target-destination is the source-byte offset
	 *      at which the FCode-offset is found, plus
	 *      the FCode-offset.
	 */
	dest = streampos + offs;

	/*  A destination of zero is invalid because there must be a
	 *      token -- such as  b(<mark)  or   b(do)  -- preceding
	 *      the target of a backward branch.
	 *  A destination at the end of the stream is unlikely but
	 *      theoretically possible, so we'll treat it as valid.
	 *  An offset of zero is also, of course, invalid.
	 */
	invalid_dest = BOOLVAL((dest <= 0)
			       || (dest > stream_max)
			       || (offs == 0));

	/*  Show the offset in hex and again as a signed decimal number.   */
	if (offs16) {
		printf("0x%04x (", (u16) (offs & 0xffff));
	} else {
		printf("0x%02x (", (u8) (offs & 0x00ff));
	}
	if ((offs < 0) || (offs > 9))
		printf(" =dec %d", offs);
	/*  If we're showing source-byte offsets, show targets of offsets  */
	if (show_offsets || invalid_dest) {
		printf("  dest = %d ", dest);
	}
	printf(")\n");

	if (invalid_dest) {
		if (offs == 0) {
			printremark("Error:  Unresolved offset.");
		} else {
			printremark
			    ("Error:  Invalid offset.  Ignoring...");
			set_streampos(streampos);
		}
	}
	return offs;
}

static void decode_default(void)
{
	output_token();
	printf("\n");
}

static void new_token(void)
{
	u16 token;
	output_token();
	token = next_token();
	printf("0x%03x\n", token);
	add_token(token, strdup(unnamed));
}

static void named_token(void)
{
	u16 token;
	u8 len;
	u8 *string;

	output_token();
	/* get forth string ( [len] [char0] ... [charn] ) */
	string = get_name(&len);
	token = next_token();
	printf("%s 0x%03x\n", string, token);
	add_token(token, string);
}

static void bquote(void)
{
	output_token();
	/* get forth string ( [len] [char0] ... [charn] ) */
	pretty_print_string();
	printf("\n");
}

static void blit(void)
{
	u32 lit;

	output_token();
	lit = get_num32();
	printf("0x%x\n", lit);
}

static void double_length_literal(void)
{
	u16 quadhh, quadhl, quadlh, quadll;

	output_token();
	quadhh = get_num16();
	quadhl = get_num16();
	quadlh = get_num16();
	quadll = get_num16();
	printf("0x%04x.%04x.%04x.%04x\n", quadhh, quadhl, quadlh, quadll);
}

static void offset16(void)
{
	decode_default();
	offs16 = TRUE;
}

static void decode_branch(void)
{
	s16 offs;
	offs = decode_offset();
	if (offs >= 0)
		indent++;
	else
		indent--;
}

static void decode_two(void)
{
	u16 token;

	output_token();
	token = next_token();
	output_token_name();
	printf("\n");
}

/* **************************************************************************
 *
 *      Function name:  decode_start
 *      Synopsis:       Display the (known valid) FCode block Header
 *
 *      Outputs:
 *         Global/Static Variables:    
 *             fclen         Length of the FCode block as shown in its Header
 *
 **************************************************************************** */

static void decode_start(void)
{
	u8 fcformat;
	u16 fcchecksum, checksum = 0;

	output_token();
	printf("  ( %d-bit offsets)\n", offs16 ? 16 : 8);

	token_streampos = get_streampos();
	decode_lines();
	fcformat = get_num8();
	printf("  format:    0x%02x\n", fcformat);


	/* Check for checksum correctness. */

	token_streampos = get_streampos();
	decode_lines();
	fcchecksum = get_num16();	/*  Read the stored checksum  */
	checksum = calc_checksum();	/*  Calculate the actual checksum  */

	if (fcchecksum == checksum) {
		printf("  checksum:  0x%04x (Ok)\n", fcchecksum);
	} else {
		printf("  checksum should be:  0x%04x, but is 0x%04x\n",
		       checksum, fcchecksum);
	}

	token_streampos = get_streampos();
	decode_lines();
	fclen = get_num32();
	printf("  len:       0x%04x ( %d bytes)\n", fclen, fclen);
}


/* **************************************************************************
 *
 *      Function name: decode_token
 *      Synopsis:      Display detokenization for one token.
 *                     Handle complicated cases and dispatch simple ones.
 *
 *      Revision History:
 *          Detect FCode-Starters in the middle of an FCode block.
 *          Some tuning of adjustment of indent, particularly wrt branches...
 *
 **************************************************************************** */

static void decode_token(u16 token)
{
	bool handy_flag = TRUE;
	switch (token) {
	case 0x0b5:
		new_token();
		break;
	case 0x0b6:		/*   Named Token  */
	case 0x0ca:		/*   External Token  */
		named_token();
		break;
	case 0x012:
		bquote();
		break;
	case 0x010:
		blit();
		break;
	case 0x0cc:
		offset16();
		break;
	case 0x013:		/* bbranch */
	case 0x014:		/* b?branch */
		decode_branch();
		break;
	case 0x0b7:		/* b(:) */
	case 0x0b1:		/* b(<mark) */
	case 0x0c4:		/* b(case) */
		decode_default();
		indent++;
		break;
	case 0x0c2:		/* b(;) */
	case 0x0b2:		/* b(>resolve) */
	case 0x0c5:		/* b(endcase) */
		indent--;
		decode_default();
		break;
	case 0x015:		/* b(loop) */
	case 0x016:		/* b(+loop) */
	case 0x0c6:		/* b(endof) */
		indent--;
		decode_offset();
		break;
	case 0x017:		/* b(do) */
	case 0x018:		/* b/?do) */
	case 0x01c:		/* b(of) */
		decode_offset();
		indent++;
		break;
	case 0x011:		/* b(') */
	case 0x0c3:		/* b(to) */
		decode_two();
		break;
	case 0x0fd:		/* version1 */
		handy_flag = FALSE;
	case 0x0f0:		/* start0 */
	case 0x0f1:		/* start1 */
	case 0x0f2:		/* start2 */
	case 0x0f3:		/* start4 */
		offs16 = handy_flag;
		printremark("Unexpected FCode-Block Starter.");
		decode_start();
		printremark("  Ignoring length field.");
		break;
	case 0:		/* end0  */
	case 0xff:		/* end1  */
		end_found = TRUE;
		decode_default();
		break;

#if  0  /*  Fooey on C's petty limitations!  */
        /*  I'd like to be able to do this:  */
	/*  Special Functions  */
	case *double_lit_code:
		double_length_literal();
		break;
#endif  /*  Fooey on C's petty limitations!  */

	default:
	{
	    /*  Have to do this clumsy thing instead  */
	    if ( token == *double_lit_code )
	    {
		double_length_literal();
		break;
	    }

		decode_default();
	}
}
}


/* **************************************************************************
 *
 *      Function name:  decode_fcode_header
 *      Synopsis:       Detokenize the FCode Header.
 *                          Check for file corruption
 *
 *      Inputs:
 *         Parameters:          NONE
 *         Global/Static Variables:        
 *             stream_max       Length of input stream
 *
 *      Outputs:
 *         Returned Value:      NONE
 *         Global/Static Variables:
 *             offs16           FALSE if Starter was version1, TRUE for all
 *                                  other valid Starters, otherwise unchanged.
 *             fclen            On error, gets set to reach end of input stream
 *                                  Otherwise, gets set by  decode_start() 
 *         Printout:
 *             On error, print a new-line to finish previous token's line.
 *
 *      Error Detection:
 *          First byte not a valid FCode Start:  Print message, restore
 *              input pointer to initial value, set fclen to [(end of
 *              input stream) - (input pointer)], leave offs16 unchanged.
 *              Return FALSE.
 *
 *      Process Explanation:
 *          This routine error-checks and dispatches to the routine that
 *              does the actual printing.
 *          Refrain from showing offset until correctness of starter-byte
 *              has been confirmed.
 *
 **************************************************************************** */

static void decode_fcode_header(void)
{
	long err_pos;
	u16 token;
	bool new_offs16 = TRUE;

	err_pos = get_streampos();
	indent = 0;
	token = next_token();

	switch (token) {
	case 0x0fd:		/* version1 */
		new_offs16 = FALSE;
	case 0x0f0:		/* start0 */
	case 0x0f1:		/* start1 */
	case 0x0f2:		/* start2 */
	case 0x0f3:		/* start4 */
		offs16 = new_offs16;
		decode_start();
		break;
	default:
		{
			char temp_bufr[128] =
			    "Invalid FCode Start Byte.  Ignoring FCode header.";
			set_streampos(err_pos);
			fclen = max - pc;
			printf("\n");
			if (show_linenumbers) {
				sprintf(&(temp_bufr[strlen(temp_bufr)]),
					"  Remaining len = 0x%04x ( %d bytes)",
					fclen, fclen);
			}
			printremark(temp_bufr);
		}
	}
}

/* **************************************************************************
 *
 *      Function name:  decode_fcode_block
 *      Synopsis:       Detokenize one FCode block.
 *
 *      Inputs:
 *         Parameters:                     NONE
 *         Global/Static Variables:
 *             fclen         Length of the FCode block as shown in its Header
 *             end_found     Whether the END0 code was seen
 *             decode_all    TRUE = continue even after END0
 *
 *      Outputs:
 *         Returned Value:                 NONE
 *         Global/Static Variables:
 *             end_found     Whether the END0 code was seen
 *         Printout:
 *             A summary message at the end of FCode detokenization
 *
 *      Error Detection:
 *          If the end of the FCode block, as calculated by its FCode length,
 *              was reached without encountering END0, print a message.
 *          Detect END0 that occurs before end of FCode block,
 *              even if  decode_all  is in effect.
 *
 *      Process Explanation:
 *          This routine dispatches to the routines that do the actual
 *              printing of the detokenization.
 *          The  end_found  flag is not a direct input, but more of an
 *              intermediate input, so to speak...  Clear it at the start.
 *          Detection of FCode-Starters in the middle of an FCode block
 *              is handled by  decode_token()  routine.
 *
 **************************************************************************** */

static void decode_fcode_block(void)
{
	u16 token;
	unsigned int fc_block_start;
	unsigned int fc_block_end;

	end_found = FALSE;
	fc_block_start = get_streampos();

	decode_fcode_header();

	fc_block_end = fc_block_start + fclen;

	while ((!end_found || decode_all)
	       && (get_streampos() < fc_block_end)) {
		token = next_token();
		decode_token(token);
	}
	if (!end_found) {
		printremark("FCode-ender not found");
	}
	{
		char temp_bufr[80];
		/*  Don't use  fclen  here, in case it got corrupted
		 *      by an "Unexpected FCode-Block Starter"
		 */
		if (get_streampos() == fc_block_end) {
			sprintf(temp_bufr,
				"Detokenization finished normally after %d bytes.",
				fc_block_end - fc_block_start);
		} else {
			sprintf(temp_bufr,
				"Detokenization finished prematurely after %d of %d bytes.",
				get_streampos() - fc_block_start,
				fc_block_end - fc_block_start);
			ended_okay = FALSE;
		}
		printremark(temp_bufr);
	}
}

/* **************************************************************************
 *
 *      Function name:  another_fcode_block
 *      Synopsis:       Indicate whether there is a follow-on FCode block
 *                      within the current PCI image.
 *
 *      Inputs:
 *         Parameters:                     NONE
 *         Global/Static Variables:
 *             token_streampos             Streampos() of token just gotten
 *             Next token in Input Stream
 *
 *      Outputs:
 *         Returned Value:                  TRUE if next token shows the start
 *                                              of a valid FCode-block
 *         Printout:
 *             Message if there is a follow-on FCode block
 *
 *      Error Detection:
 *          If next token is neither a valid FCode-block starter nor the
 *              start of a zero-fill field, print a message.
 *
 *      Process Explanation:
 *          Extract the next token from the Input Stream but do not
 *              consume it.  Then examine the token.
 *
 **************************************************************************** */

static bool another_fcode_block(void)
{
	bool retval = FALSE;
	u16 token;

	token = next_token();
	set_streampos(token_streampos);

	switch (token) {
	case 0x0fd:		/* version1 */
	case 0x0f0:		/* start0 */
	case 0x0f1:		/* start1 */
	case 0x0f2:		/* start2 */
	case 0x0f3:		/* start4 */
		retval = TRUE;
		printremark
		    ("Subsequent FCode Block detected.  Detokenizing.");
		break;
	case 0:		/* Start of a zero-fill field  */
		/* retval already = FALSE .  Nothing else to be done.  */
		break;
	default:
		{
			char temp_bufr[80];
			sprintf(temp_bufr,
				"Unexpected token, 0x%02x, after end of FCode block.",
				token);
			printremark(temp_bufr);
		}
	}
	return (retval);
}

/* **************************************************************************
 *
 *      Function name:  detokenize
 *      Synopsis:       Detokenize one input file 
 *                      
 *
 *
 **************************************************************************** */

void detokenize(void)
{
	fclen = stream_max;

	if (setjmp(eof_exception) == 0) {
		while (more_to_go()) {
			if (ended_okay) {
				init_fcode_block();
			}
			ended_okay = TRUE;

			adjust_for_pci_header();

			/*   Allow for multiple FCode Blocks within the PCI image.
			 *   The first one had better be a valid block, but the
			 *   next may or may not be...
			 */
			do {
				decode_fcode_block();
			} while (another_fcode_block());

			adjust_for_pci_filler();

		}
	}


}
