package pascal;

import rebelsky.compiler.lexer.CharToken;
import rebelsky.compiler.lexer.Identifier;
import rebelsky.compiler.lexer.IntegerToken;
import rebelsky.compiler.lexer.RealToken;
import rebelsky.compiler.lexer.StringToken;
import rebelsky.compiler.misc.Checker;
import rebelsky.compiler.misc.Symbol;
import rebelsky.compiler.misc.Traverser;
import rebelsky.compiler.parser.Node;
import rebelsky.compiler.types.BasicTypes;
import rebelsky.compiler.types.Type;

/**
 * Something that should be able to type check Pascal programs.
 * program.
 *
 * @author Samuel A. Rebelsky
 * @version 1.0 of April 2004
 */
public class PascalTypeChecker
  extends Traverser
{
  /**
   * Undefined types.  Useful when you can't find another type.
   */
  public static final Type UNDEFINED = new Type("Undefined");

  /**
   * The default checker for this traverser.
   */
  Checker def = new Checker() {
      public void check(Traverser t, Node n) 
        throws Exception
      {
        if (n == null) return;
        Symbol sym = n.getSymbol();
        if (sym instanceof CharToken) 
          n.setAttribute("type", BasicTypes.CHAR);
        else if (sym instanceof IntegerToken) 
          n.setAttribute("type", BasicTypes.INTEGER);
        else if (sym instanceof RealToken) 
          n.setAttribute("type", BasicTypes.REAL);
        else if (sym instanceof StringToken) 
          n.setAttribute("type", BasicTypes.STRING);
        else {
          int numchildren = n.numChildren();
          for (int i = 0; i < numchildren; i++) {
            t.traverse(n.getChild(i));
          } // for
        } // not one of the basic types
      } // check(Traverser, Node)
    };  // def = ...

  public PascalTypeChecker()
  {
    // Insert some standard types.
    try {
      this.setType((Identifier) PascalTokens.TTRUE, BasicTypes.BOOLEAN);
      this.setType((Identifier) PascalTokens.TFALSE, BasicTypes.BOOLEAN);
    }
    catch (Exception e) {
    }

    // Update the default traversal mechanism.
    this.setDefault(def);

    // Assign a type to ARRAY_TYPE nodes.
    this.setChecker(PascalNonterminals.ARRAY_TYPE, new Checker() {
        public void check(Traverser t, Node n) 
          throws Exception
        {
          // Do the default checking
          def.check(t, n);
          // Determine the lower and upper bounds.  Right now, this
          // works only for ranges.
          // Add a type for this node
         
          n.setAttribute("type", new 
        } // check(Traverse, Node);
      }); // ARRAY_TYPE

    // Look up named variables in the symbol table.
    this.setChecker(PascalNonterminals.VARIABLE_NAME, new Checker() {
        public void check(Traverser t, Node n) 
          throws Exception
        {
          // Traverse the child, just in case.
          t.traverse(n.getChild(0));
          // Look up the type and add it to this node
          Type type = t.getType((Identifier) n.getChild(0).getSymbol());
          if (type != null)
            n.setAttribute("type", type);
        } // check(Traverser, Node)
      }); // VARIABLE_NAME

    // For procedure declarations, we must check both variable declarations
    // and parameters.  This checker is not yet completely implemented.
    this.setChecker(PascalNonterminals.PROCEDURE_DECLARATION, new Checker() {
        public void check(Traverser t, Node n)
          throws Exception
        {
          // Begin a new scope
          t.beginScope();
          // Count the number of variables declared.
          int numchildren = n.numChildren();
          // Visit all the children
          for (int i = 0; i < numchildren; i++)
            t.traverse(n.getChild(i));
          // End the scope we just created
          t.endScope();
        } // check(Traverser, Node)
      }); // PROCEDURE_DECLARATION
  
      // For type identifiers, we set the type based on the identifier.
    this.setChecker(PascalNonterminals.TYPE_IDENTIFIER, new Checker() {
        public void check(Traverser t, Node n) 
          throws Exception
        {
          // Traverse the child, just in case.
          t.traverse(n.getChild(0));
          // Extract the type and add it to this node.
          Symbol typesym = n.getChild(0).getSymbol();
          if (typesym == PascalTokens.TBOOLEAN)
            n.setAttribute("type", BasicTypes.BOOLEAN);
          else if (typesym == PascalTokens.TCHAR)
            n.setAttribute("type", BasicTypes.CHAR);
          else if (typesym == PascalTokens.TINTEGER)
            n.setAttribute("type", BasicTypes.INTEGER);
          else if (typesym == PascalTokens.TREAL)
            n.setAttribute("type", BasicTypes.REAL);
          else if (typesym == PascalTokens.TSTRING)
            n.setAttribute("type", BasicTypes.STRING);
          else
            n.setAttribute("type", UNDEFINED);
        } // check(Traverser);
      }); // TYPE_IDENTIFIER

    // For variable declarations, we enter the types of all the IDS in
    // the symbol table.
    this.setChecker(PascalNonterminals.VARIABLE_DECLARATION, new Checker() {
        public void check(Traverser t, Node n) 
          throws Exception
        {
          // Count the number of variables declared.
          int numvars = n.numChildren() - 1;
          // Visit all the children, just in case.
          for (int i = 0; i <= numvars; i++)
            t.traverse(n.getChild(i));
          // Get the type
          Type type = (Type) n.getChild(numvars).getAttribute("type");
          // Enter the types
          for (int i = 0; i < numvars; i++) {
            t.setType((Identifier) n.getChild(i).getSymbol(), type);
          } // for
        } // check(Traverser)
      }); // VARIABLE_DECLARATION

  } // PascalTypeChecker()
} // class PascalTypeChecker

