import java.io.Reader;
import java.io.StreamTokenizer;
import java.io.StringReader;

import trees.Tree;
import junit.framework.TestCase;

/*
 * Created on Feb 4, 2004
 */

/**
 * @author David Matuszek
 * @version Feb 6, 2004
 */
public class ParserTest extends TestCase {
    Parser parser;
    /**
     * Constructor for ParserTest.
     * @param arg0
     */
    public ParserTest(String arg0) {
        super(arg0);
    }

    public void testParser() {
        parser = new Parser("");
        parser = new Parser("2 + 2");
    }

    public void testExpression() {
        Tree<Token> expected;
        
        use("250");
        assertTrue(parser.isExpression());
        assertEquals(tree("250"), stackTop());
        
        use("hello");
        assertTrue(parser.isExpression());
        assertEquals(tree("hello"), stackTop());

        use("(xyz + 3)");
        assertTrue(parser.isExpression());
        assertEquals(tree("+", "xyz", "3"), stackTop());

        use("a + b + c");
        assertTrue(parser.isExpression());
        assertEquals(tree("+", tree("+", "a", "b"), "c"), stackTop());

        use("a * b * c");
        assertTrue(parser.isExpression());
        assertEquals(tree("*", tree("*", "a", "b"), "c"), stackTop());

        use("3 * 12 - 7");
        assertTrue(parser.isExpression());
        assertEquals(tree("-", tree("*", "3", "12"), tree("7")), stackTop());

        use("12 * 5 - 3 * 4 / 6 + 8");
        assertTrue(parser.isExpression());
        expected = tree("+",
                      tree("-",
                         tree("*", "12", "5"),
                         tree("/",
                            tree("*", "3", "4"),
                            "6"
                           )
                        ),
                      "8"
                     );
        assertEquals(expected, stackTop());
                     
        use("12 * ((5 - 3) * 4) / 6 + (8)");
        assertTrue(parser.isExpression());
        expected = tree("+",
                      tree("/",
                         tree("*",
                            "12",
                            tree("*",
                               tree("-","5","3"),
                               "4")),
                         "6"),
                      "8");
        assertEquals(expected, stackTop());
        
        use("");
        assertFalse(parser.isExpression());
        
        use("#");
        assertFalse(parser.isExpression());

        try {
            use("17 +");
            assertFalse(parser.isExpression());
            fail();
        }
        catch (SyntaxException e) {
        }
        try {
            use("22 *");
            assertFalse(parser.isExpression());
            fail();
        }
        catch (SyntaxException e) {
        }
    }
    
    public void testUnaryOperator() {
        
        use("-250");
        assertTrue(parser.isExpression());
        assertEquals(tree("-", "250"), stackTop());
        
        use("+250");
        assertTrue(parser.isExpression());
        assertEquals(tree("+", "250"), stackTop());
        
        use("- hello");
        assertTrue(parser.isExpression());
        assertEquals(tree("-", "hello"), stackTop());

        use("-(xyz + 3)");
        assertTrue(parser.isExpression());
        assertEquals(tree("-", tree("+", "xyz", "3")), stackTop());

        use("(-xyz + 3)");
        assertTrue(parser.isExpression());
        assertEquals(tree("+", tree("-", "xyz"), "3"), stackTop());
    }
    
    public void testNextToken() {
        use("12 bogus while + \n");
        assertEquals(new Token(Token.Type.NUMBER, "12"), parser.nextToken());
        assertEquals(new Token(Token.Type.NAME, "bogus"), parser.nextToken());
        assertEquals(new Token(Token.Type.KEYWORD, "while"), parser.nextToken());
        assertEquals(new Token(Token.Type.SYMBOL, "+"), parser.nextToken());
        assertEquals(new Token(Token.Type.EOL, "\n"), parser.nextToken());
        assertEquals(new Token(Token.Type.EOF, "EOF"), parser.nextToken());
    }

    public void testTerm() {        
        use("12");
        assertTrue(parser.isTerm());
        assertEquals(tree("12"), stackTop());

        use("3*12");
        assertTrue(parser.isTerm());
        assertEquals(tree("*", "3", "12"), stackTop());

        use("x * y * z");
        assertTrue(parser.isTerm());
        assertEquals(tree("*", tree("*", "x", "y"), "z"), stackTop());
        
        use("20 * 3 / 4");
        assertTrue(parser.isTerm());
        assertEquals(tree("/", tree("*", "20", "3"), tree("4")), stackTop());

        use("20 * 3 / 4 + 5");
        assertTrue(parser.isTerm());
        assertEquals(tree("/", tree("*", "20", "3"), "4"), stackTop());
        followedBy("+ 5");
        
        use("");
        assertFalse(parser.isTerm());
        followedBy("");
        
        use("#");
        assertFalse(parser.isTerm());followedBy("#");

    }

    public void testFactor() {
        use("12");
        assertTrue(parser.isFactor());
        assertEquals(tree("12"), stackTop());

        use("hello");
        assertTrue(parser.isFactor());
        assertEquals(tree("hello"), stackTop());
        
        use("(xyz + 3)");
        assertTrue(parser.isFactor());
        assertEquals(tree("+", "xyz", "3"), stackTop());
        
        use("12 * 5");
        assertTrue(parser.isFactor());
        assertEquals(tree("12"), stackTop());
        followedBy("* 5");
        
        use("17 +");
        assertTrue(parser.isFactor());
        assertEquals(tree("17"), stackTop());
        followedBy("+");

        use("");
        assertFalse(parser.isFactor());
        followedBy("");
        
        use("#");
        assertFalse(parser.isFactor());
        followedBy("#");
    }

    public void testAdd_operator() {
        use("+ - + $");
        assertTrue(parser.isAddOperator());
        assertTrue(parser.isAddOperator());
        assertTrue(parser.isAddOperator());
        assertFalse(parser.isAddOperator());
        followedBy("$");
    }

    public void testMultiply_operator() {
        use("* / $");
        assertTrue(parser.isMultiplyOperator());
        assertTrue(parser.isMultiplyOperator());
        assertFalse(parser.isMultiplyOperator());
        followedBy("$");
    }
    
//  ----- "Helper" methods

    /**
     * This method is given a String containing some or all of the
     * tokens that should yet be returned by the Tokenizer, and tests
     * whether the Tokenizer in fact has those Tokens. To succeed,
     * everything in the given String must still be in the Tokenizer,
     * but there may be additional (untested) Tokens to be returned.
     * This method is primarily to test whether Tokens are pushed
     * back appropriately.
     * 
     * @param expectedTokens The Tokens we expect to get from the Tokenizer.
     */
    private void followedBy(String expectedTokens) {
        int expectedType;
        int actualType;
        StreamTokenizer actual = parser.tokenizer;

        Reader reader = new StringReader(expectedTokens);
        StreamTokenizer expected = new StreamTokenizer(reader);
        Parser.setTokenizerParameters(expected);

        try {
            while (true) {
                expectedType = expected.nextToken();
                if (expectedType == StreamTokenizer.TT_EOF) break;
                actualType = actual.nextToken();
                assertEquals(typeName(expectedType), typeName(actualType));
                if (actualType == StreamTokenizer.TT_WORD) {
                    assertEquals(expected.sval, actual.sval);
                }
                else if (actualType == StreamTokenizer.TT_NUMBER) {
                    assertEquals(expected.nval, actual.nval);
                }
            }
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    private String typeName(int type) {
        switch(type) {
            case StreamTokenizer.TT_EOF: return "EOF";
            case StreamTokenizer.TT_EOL: return "EOL";
            case StreamTokenizer.TT_WORD: return "WORD";
            case StreamTokenizer.TT_NUMBER: return "NUMBER";
            default: return "'" + (char)type + "'";
        }
    }
    
    /**
     * Sets the <code>parser</code> instance to use the given string.
     * 
     * @param s The string to be parsed.
     */
    private void use(String s) {
        parser = new Parser(s);
    }
    
    /**
     * Returns the current top of the stack.
     *
     * @return The top of the stack.
     */
    private Object stackTop() {
        return parser.stack.peek();
    }
    
    /**
     * Returns a Tree node consisting of a single leaf; the
     * node will contain a Token with a String as its value. <br>
     * Given a Tree, return the same Tree.<br>
     * Given a Token, return a Tree with the Token as its value.<br>
     * Given a String, make it into a Token, return a Tree
     * with the Token as its value.
     * 
     * @param value A Tree, Token, or String from which to
              construct the Tree node.
     * @return A Tree leaf node containing a Token whose value
     *         is the parameter.
     */
    private Tree<Token> tree(Object value) {
        if (value instanceof Tree) {
            return (Tree) value;
        }
        if (value instanceof Token) {
            return new Tree<Token>((Token) value);
        }
        else if (value instanceof String) {
            return new Tree<Token>(makeToken((String) value));
        }
        assert false: "Illegal argument: tree(" + value + ")";
        return null; 
    }
    
    /**
     * Builds a Tree that can be compared with the one the
     * Parser produces. Any String or Token arguments will be
     * converted to Tree nodes containing Tokens.
     * 
     * @param op The String value to use in the Token in the root.
     * @param children The objects to be made into children.
     * @return The resultant Tree.
     */
    private Tree<Token> tree(String op, Object... children) {
        Tree<Token> tree = new Tree<Token>(makeToken(op));
        for (int i = 0; i < children.length; i++) {
            tree.addChild(tree(children[i]));
        }
        return tree;
    }
    
    /**
     * Quick'n'dirty routine to make a Token from a String. The
     * type (name, number, or symbol) is inferred from the first
     * character; no error checking is done.
     * 
     * @param s The string to turn into a Token.
     * @return A Token whose value is the given string and whose
     *         type has been inferred from the first character.
     */
    private Token makeToken(String s) {
        char ch = s.charAt(0);
        if (Character.isDigit(ch))
            return new Token(Token.Type.NUMBER, s);
        if (Character.isLetter(ch))
            return new Token(Token.Type.NAME, s);
        else
            return new Token(Token.Type.SYMBOL, s);
    }
}

