// file:    SexpParser.java
// author:  Robert Keller
// purpose: Illustrating S expression parser
//
// The grammar used here is:
//
// S -> '(' {S} ')'  |  A		S expressions
//
// A -> 'a' | 'b' | 'c'			atoms
//
// Here {....} means 0-or-more of .... and | is alternatives
//
// Each parse method returns true or false indicating whether or not
// it parsed its syntactic category from the input stream


import java.io.*;

class SexpParser
{
PushbackInputStream in;		// stream from which parsing takes characters


SexpParser(InputStream in)	// constructor
  {
  this.in = new PushbackInputStream(in);
  }


// parse method for S expression

boolean S()
  {
  int c;					// character read

  if( end(c = read()) )
    return false;				// eof is not S expression

  if( c == '(' )				// see if "(..."
    {
    while( !end(c = read()) )
      {
      if( c == ')' )				
        return true;				// terminated "...)"

      unread(c);
      if( !S() )				// parse inner S expressions
        return false;
      }
    return false;				// something else
    }
  else
    {
    unread(c);
    return A();					// not "(...", must be atom
    }
  }


// Parse method for atom

boolean A()
  {
  int c = read();
  switch( c )
    {
    case 'a':
    case 'b':
    case 'c':
		return true;
    default:
                unread(c);
		return false;
    }  
  }



// loop tries to parse one S expression per line, until end-of-file

void loop()
  {
  int c;
  while( !eof(c = read()) )		// while not end-of-file
    {
    unread(c);
    if( S() )
      {
      System.out.print("ok");
      skipWhitespace();
      if( (c = read()) != '\n' )
	{
	System.out.println(", except garbage at the end");
	}
      else
        System.out.println();
      unread(c);
      }
    else
      {
      System.out.println("bad");
      }
    skipToEOL();			// skip to end-of-line
    }
  }



// main program

public static void main(String[] arg)
  {
  SexpParser parser = new SexpParser(System.in);

  parser.loop();
  }



// read next character

int read()
  {
  int c;
  try
    {
    c = in.read();
    return c;
    }
  catch(IOException e)
    {
    System.err.println("Input Exception");
    return -1; 					// treat as end-of-file
    }
  }


// unread character

void unread(int c)
  {
  try
    {
    in.unread(c);
    }
  catch(IOException e)
    {
    System.err.println("Exception unreading");
    }
  }


// skip to end-of-line

void skipToEOL()
  {
  int c;
  while( (c = read()) >= 0 )	// stop if end-of-file
    {
    switch( c )
      {
      case '\n':
		return;
      }
    }
  }



// skip any whitespace

void skipWhitespace()
  {
  int c;
  while( (c = read()) >= 0 )	// stop if end-of-file
    {
    switch( c )
      {
      case '\t':
      case ' ' :
		break;		// whitespace, keep going
      default:
		unread(c);	// other, return
		return;
      }
    }
  }

// end tells whether c is end-of-line or end-of-file

static boolean end(int c)
  {
  if( eof(c) )
    return true;

  if( c == '\n' )
    return true;

  return false;
  }


// eof tells whether c is end-of-file

static boolean eof(int c)
  {
  return c < 0;
  }


} // SexpParser

