/* parse.c */

/* nettle, low-level cryptographics library
 *
 * Copyright (C) 2002, 2003 Niels Möller
 *  
 * The nettle library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or (at your
 * option) any later version.
 * 
 * The nettle library 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 Lesser General Public
 * License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with the nettle library; see the file COPYING.LIB.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02111-1301, USA.
 */

#if HAVE_CONFIG_H
# include "config.h"
#endif

#include <assert.h>
#include <stdlib.h>

#include "parse.h"

#include "input.h"

void
sexp_compound_token_init(struct sexp_compound_token *token)
{
  token->type = 0;
  nettle_buffer_init(&token->display);
  nettle_buffer_init(&token->string);
}

void
sexp_compound_token_clear(struct sexp_compound_token *token)
{
  nettle_buffer_clear(&token->display);
  nettle_buffer_clear(&token->string);
}

void
sexp_parse_init(struct sexp_parser *parser,
		struct sexp_input *input,
		enum sexp_mode mode)
{
  parser->input = input;
  parser->mode = mode;

  /* Start counting with 1 for the top level, to make comparisons
   * between transport and level simpler.
   *
   * FIXME: Is that trick ugly? */
  parser->level = 1;
  parser->transport = 0;
}

/* Get next token, and check that it is of the expected kind. */
static void
sexp_check_token(struct sexp_parser *parser,
		 enum sexp_token token,
		 struct nettle_buffer *string)
{
  sexp_get_token(parser->input,
		 parser->transport ? SEXP_CANONICAL : parser->mode,
		 string);

  if (parser->input->token != token)
    die("Syntax error.\n");
}

/* Performs further processing of the input, in particular display
 * types and transport decoding.
 *
 * This is complicated a little by the requirement that a
 * transport-encoded block, {xxxxx}, must include exactly one
 * expression. We check at the end of strings and list whether or not
 * we should expect a SEXP_CODING_END as the next token. */
void
sexp_parse(struct sexp_parser *parser,
	   struct sexp_compound_token *token)
{
  for (;;)
    {
      sexp_get_token(parser->input,
		     parser->transport ? SEXP_CANONICAL : parser->mode,
		     &token->string);

      switch(parser->input->token)
	{
	case SEXP_LIST_END:
	  if (parser->level == parser->transport)
	    die("Unmatched end of list in transport encoded data.\n");
	  parser->level--;

	  if (!parser->level)
	    die("Unmatched end of list.\n");

	  token->type = SEXP_LIST_END;

	check_transport_end:
	  if (parser->level == parser->transport)
	    {
	      sexp_check_token(parser, SEXP_CODING_END, &token->string);
	      assert(parser->transport);
	      assert(parser->level == parser->transport);

	      parser->level--;
	      parser->transport = 0;
	    }
	  return;
    
	case SEXP_EOF:
	  if (parser->level > 1)
	    die("Unexpected end of file.\n");

	  token->type = SEXP_EOF;
	  return;

	case SEXP_LIST_START:
	  parser->level++;
	  token->type = SEXP_LIST_START;
	  return;

	case SEXP_DISPLAY_START:
	  sexp_check_token(parser, SEXP_STRING, &token->display);
	  sexp_check_token(parser, SEXP_DISPLAY_END, &token->display);
	  sexp_check_token(parser, SEXP_STRING, &token->string);

	  token->type = SEXP_DISPLAY;
	  goto check_transport_end;

	case SEXP_STRING:
	  token->type = SEXP_STRING;
	  goto check_transport_end;

	case SEXP_COMMENT:
	  token->type = SEXP_COMMENT;
	  return;

	case SEXP_TRANSPORT_START:
	  if (parser->mode == SEXP_CANONICAL)
	    die("Base64 not allowed in canonical mode.\n");
	  parser->level++;
	  parser->transport = parser->level;

	  continue;

	case SEXP_CODING_END:
	  die("Unexpected end of transport encoding.\n");
	  
	default:
	  /* Internal error. */
	  abort();
	}
    }
}
