/*++

Copyright (c) 2013 Minoca Corp. All Rights Reserved

Module Name:

    test.c

Abstract:

    This module implements the test (aka left square bracket [) program.

Author:

    Evan Green 15-Jun-2013

Environment:

    POSIX

--*/

//
// ------------------------------------------------------------------- Includes
//

#include <minoca/lib/types.h>

#include <assert.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include "swlib.h"

//
// ---------------------------------------------------------------- Definitions
//

#define TEST_VERSION_MAJOR 1
#define TEST_VERSION_MINOR 0

#define TEST_USAGE                                                             \
    "usage: test [<test>]\n"                                                   \
    "       [ <test> ]\n"                                                      \
    "The test utility performs basic file, integer, and string tests to \n"    \
    "augment the shell's functionality. Options are: \n"                       \
    "  --help -- Show this help and exit.\n"                                   \
    "  --version -- Show the application version and exit.\n\n"                \
    "Valid tests are:\n"                                                       \
    "  -b file -- The file exists and is a block device.\n"                    \
    "  -c file -- The file exists and is a character device.\n"                \
    "  -d file -- The file exists and is a directory.\n"                       \
    "  -f file -- The file exists and is a regular file.\n"                    \
    "  -g file -- The file exists and has its set-group-ID flag set.\n"        \
    "  -h file -- The file exists and is a symbolic link.\n"                   \
    "  -L file -- The file exists and is a symbolic link (same as -h).\n"      \
    "  -p file -- The file exists and is a FIFO.\n"                            \
    "  -r file -- The file exists and is readable.\n"                          \
    "  -S file -- The file exists and is a socket.\n"                          \
    "  -s file -- The file exists and has a size greater than zero.\n"         \
    "  -t file_descriptor -- The file descriptor is valid and points to a \n"  \
    "      terminal device.\n"                                                 \
    "  -u file -- The file exists and has its set-user-ID flag set.\n"         \
    "  -w file -- The file exists and is writable.\n"                          \
    "  -x file -- The file exists and is executable.\n"                        \
    "  file1 -fe file2 -- True if file1 and file2 have the same device and \n" \
    "      file serial numbers.\n"                                             \
    "  file1 -nt file2 -- True if file1 has a later modification date than \n" \
    "      file2.\n"                                                           \
    "  file1 -ot file2 -- True if file1 has an earlier modification date \n"   \
    "      than file2.\n"                                                      \
    "  -n string -- True if the length of the given string is non-zero.\n"     \
    "  -z string -- True if the length of the string is zero.\n"               \
    "  string -- True if the string is not the null string.\n"                 \
    "  string1 = string2 -- True if the two strings are identical.\n"          \
    "  string1 != string2 -- True if the two strings are not identical.\n"     \
    "  number1 -eq number2 -- True if the two numbers are equal.\n"            \
    "  number1 -ne number2 -- True if the two numbers are not equal.\n"        \
    "  number1 -gt number2 -- True if number1 is greater than number2.\n"      \
    "  number1 -ge number2 -- True if number1 is greater than or equal to \n"  \
    "      number2.\n"                                                         \
    "  number1 -lt number2 -- True if number1 is less than number2.\n"         \
    "  number1 -le number2 -- True if number1 is less than or equal to "       \
    "number2.\n\n"                                                             \
    "Additionally, tests can be combined in the following ways:\n"             \
    "  expression1 -a expression2 -- True if both expression1 and \n"          \
    "      expression2 are true. This has a higher precedence than -o.\n"      \
    "  expression1 -o expression2 -- True if either expression1 or \n"         \
    "      expression2 are true.\n"                                            \
    "  ! expression - True if the expression is false.\n"                      \
    "  ( expression ) - True if the inner expression is True. Parentheses \n"  \
    "      can be used to alter the normal associativity and precedence.\n\n"  \

//
// Define away the test utility return values since they're backwards and
// therefore a little tough to look at.
//

#define TEST_UTILITY_FALSE 1
#define TEST_UTILITY_TRUE 0
#define TEST_UTILITY_ERROR 2

//
// ------------------------------------------------------ Data Type Definitions
//

typedef enum _TEST_UTILITY_TEST {
    TestUtilityInvalid,
    TestUtilityBang,
    TestUtilityOpenParentheses,
    TestUtilityCloseParentheses,
    TestUtilityAnd,
    TestUtilityOr,
    TestFileMinValue,
    TestFileIsBlockDevice,
    TestFileIsCharacterDevice,
    TestFileIsDirectory,
    TestFileExists,
    TestFileIsRegularFile,
    TestFileHasSetGroupId,
    TestFileIsSymbolicLink,
    TestFileIsFifo,
    TestFileCanRead,
    TestFileIsSocket,
    TestFileIsNonEmpty,
    TestFileDescriptorIsTerminal,
    TestFileHasSetUserId,
    TestFileCanWrite,
    TestFileCanExecute,
    TestFileEqual,
    TestFileNewer,
    TestFileOlder,
    TestFileMaxValue,
    TestStringMinValue,
    TestStringNonZeroLength,
    TestStringZeroLength,
    TestStringEquals,
    TestStringNotEquals,
    TestStringMaxValue,
    TestIntegerMinValue,
    TestIntegerEquals,
    TestIntegerNotEquals,
    TestIntegerGreaterThan,
    TestIntegerGreaterThanOrEqualTo,
    TestIntegerLessThan,
    TestIntegerLessThanOrEqualTo,
    TestIntegerMaxValue,
} TEST_UTILITY_TEST, *PTEST_UTILITY_TEST;

typedef enum _TEST_PARSE_ELEMENT_TYPE {
    TestParseElementInvalid,
    TestParseElementOperator,
    TestParseElementToken,
    TestParseElementResult,
    TestParseElementEnd,
} TEST_PARSE_ELEMENT_TYPE, *PTEST_PARSE_ELEMENT_TYPE;

typedef struct _TEST_PARSE_ELEMENT {
    TEST_PARSE_ELEMENT_TYPE Type;
    PSTR Token;
    TEST_UTILITY_TEST Operator;
    INT Result;
} TEST_PARSE_ELEMENT, *PTEST_PARSE_ELEMENT;

//
// ----------------------------------------------- Internal Function Prototypes
//

INT
TestEvaluateExpression (
    INT ArgumentCount,
    CHAR **Arguments
    );

BOOL
TestShiftOrReduce (
    PTEST_PARSE_ELEMENT Stack,
    ULONG StackSize,
    PTEST_PARSE_ELEMENT NextElement,
    PBOOL Shift
    );

BOOL
TestReduce (
    PTEST_PARSE_ELEMENT Stack,
    PULONG StackSize
    );

TEST_UTILITY_TEST
TestGetOperator (
    PSTR String
    );

ULONG
TestGetOperandCount (
    TEST_UTILITY_TEST Operator
    );

ULONG
TestGetOperatorPrecedence (
    TEST_UTILITY_TEST Operator
    );

INT
TestEvaluateUnaryOperator (
    TEST_UTILITY_TEST Operator,
    PSTR Operand
    );

INT
TestEvaluateBinaryOperator (
    TEST_UTILITY_TEST Operator,
    PSTR LeftOperand,
    PSTR RightOperand
    );

INT
TestEvaluateStringTest (
    TEST_UTILITY_TEST Operator,
    PSTR String1,
    PSTR String2
    );

INT
TestEvaluateIntegerTest (
    TEST_UTILITY_TEST Operator,
    PSTR LeftIntegerString,
    PSTR RightIntegerString
    );

INT
TestEvaluateAndOr (
    TEST_UTILITY_TEST Operator,
    INT Left,
    INT Right
    );

VOID
TestConvertTokenToResult (
    PTEST_PARSE_ELEMENT Element
    );

//
// -------------------------------------------------------------------- Globals
//

PSTR TestOperatorStrings[] = {
    NULL,
    "!",
    "(",
    ")",
    "-a",
    "-o",
    NULL,
    "-b",
    "-c",
    "-d",
    "-e",
    "-f",
    "-g",
    "-h",
    "-p",
    "-r",
    "-S",
    "-s",
    "-t",
    "-u",
    "-w",
    "-x",
    "-fe",
    "-nt",
    "-ot",
    NULL,
    NULL,
    "-n",
    "-z",
    "=",
    "!=",
    NULL,
    NULL,
    "-eq",
    "-ne",
    "-gt",
    "-ge",
    "-lt",
    "-le",
    NULL,
};

BOOL TestDebugPrintEvaluations = FALSE;

//
// ------------------------------------------------------------------ Functions
//

INT
TestMain (
    INT ArgumentCount,
    CHAR **Arguments
    )

/*++

Routine Description:

    This routine implements the test application entry point.

Arguments:

    ArgumentCount - Supplies the number of arguments on the command line.

    Arguments - Supplies an array of pointers to strings representing the
        arguments.

Return Value:

    0 or 1 if the evaluation succeeds.

    >1 on failure.

--*/

{

    PSTR LastSlash;
    INT Result;

    //
    // Look for --help or --version as the only argument.
    //

    if (ArgumentCount == 2) {
        if (strcmp(Arguments[1], "--help") == 0) {
            printf(TEST_USAGE);
            return TEST_UTILITY_ERROR;

        } else if (strcmp(Arguments[1], "--version") == 0) {
            SwPrintVersion(TEST_VERSION_MAJOR, TEST_VERSION_MINOR);
            return TEST_UTILITY_ERROR;
        }
    }

    //
    // Look for an open bracket.
    //

    if (ArgumentCount == 0) {
        return TEST_UTILITY_ERROR;
    }

    LastSlash = strrchr(Arguments[0], '/');
    if (LastSlash == NULL) {
        LastSlash = Arguments[0];

    } else {
        LastSlash += 1;
    }

    if (strchr(LastSlash, '[') != NULL) {

        //
        // There had better be a closing bracket.
        //

        if (strcmp(Arguments[ArgumentCount - 1], "]") != 0) {
            SwPrintError(0, Arguments[ArgumentCount - 1], "Expected ']'");
            return TEST_UTILITY_ERROR;
        }

        ArgumentCount -= 1;
    }

    if (ArgumentCount == 0) {
        return TEST_UTILITY_FALSE;
    }

    Result = TestEvaluateExpression(ArgumentCount - 1, Arguments + 1);
    return Result;
}

//
// --------------------------------------------------------- Internal Functions
//

INT
TestEvaluateExpression (
    INT ArgumentCount,
    CHAR **Arguments
    )

/*++

Routine Description:

    This routine evaluates an expression for the test application.

Arguments:

    ArgumentCount - Supplies the number of remaining arguments on the command
        line.

    Arguments - Supplies an array of pointers to strings representing the
        arguments.

    ArgumentsConsumed - Supplies a pointer where the number of arguments
        consumed will be returned.

Return Value:

    0 or 1 if the evaluation succeeds.

    >1 on failure.

--*/

{

    ULONG ArgumentIndex;
    TEST_PARSE_ELEMENT NextElement;
    TEST_UTILITY_TEST Operator;
    PTEST_PARSE_ELEMENT ParseStack;
    BOOL Result;
    INT ReturnValue;
    BOOL Shift;
    ULONG StackIndex;

    ParseStack = NULL;

    //
    // Zero arguments represents an implicit test for a non-null string
    // failing.
    //

    if (ArgumentCount == 0) {
        return TEST_UTILITY_FALSE;

    //
    // For a single argument test to see if it's non-null.
    //

    } else if (ArgumentCount == 1) {
        if (strlen(Arguments[0]) != 0) {
            return TEST_UTILITY_TRUE;

        } else {
            return TEST_UTILITY_FALSE;
        }

    //
    // For two arguments, process a unary operator or a bang plus an implicit
    // non-null test.
    //

    } else if (ArgumentCount == 2) {
        Operator = TestGetOperator(Arguments[0]);
        if (Operator == TestUtilityBang) {
            if (strlen(Arguments[1]) != 0) {
                return TEST_UTILITY_FALSE;

            } else {
                return TEST_UTILITY_TRUE;
            }
        }

        if ((Operator == TestUtilityInvalid) ||
            (TestGetOperandCount(Operator) != 1)) {

            SwPrintError(0, NULL, "%s: Unary operator expected", Arguments[0]);
            return TEST_UTILITY_ERROR;
        }

        return TestEvaluateUnaryOperator(Operator, Arguments[1]);

    //
    // For three arguments:
    // * If $2 is a binary primary, perform the binary test of $1 and $3.
    // * If $1 is '!', negate the two-argument test of $2 and $3.
    // * If $1 is '(' and $3 is ')', perfomr the unary test of $2.
    //

    } else if (ArgumentCount == 3) {
        Operator = TestGetOperator(Arguments[1]);
        if ((Operator != TestUtilityInvalid) &&
            (TestGetOperandCount(Operator) == 2)) {

            ReturnValue = TestEvaluateBinaryOperator(Operator,
                                                     Arguments[0],
                                                     Arguments[2]);

            return ReturnValue;

        } else {
            Operator = TestGetOperator(Arguments[0]);
            if (Operator == TestUtilityBang) {
                ReturnValue = TestEvaluateExpression(ArgumentCount - 1,
                                                     Arguments + 1);

                if (ReturnValue == TEST_UTILITY_FALSE) {
                    return TEST_UTILITY_TRUE;

                } else if (ReturnValue == TEST_UTILITY_TRUE) {
                    return TEST_UTILITY_FALSE;

                } else {
                    return ReturnValue;
                }

            } else if (Operator == TestUtilityOpenParentheses) {
                Operator = TestGetOperator(Arguments[2]);
                if (Operator != TestUtilityCloseParentheses) {
                    SwPrintError(0,
                                 NULL,
                                 "%s: Close parentheses expected",
                                 Arguments[2]);

                    return TEST_UTILITY_ERROR;
                }

                ReturnValue = TestEvaluateExpression(ArgumentCount - 2,
                                                     Arguments + 1);

                return ReturnValue;
            }
        }

    //
    // For four arguments:
    // * If $1 is '!', negate the three-argument test of $2, $3, and $4.
    // * If $1 is '(' and $4 is ')', perform the two argument test of $2 and $3.
    //

    } else if (ArgumentCount == 4) {
        Operator = TestGetOperator(Arguments[0]);
        if (Operator == TestUtilityBang) {
            ReturnValue = TestEvaluateExpression(ArgumentCount - 1,
                                                 Arguments + 1);

            if (ReturnValue == TEST_UTILITY_FALSE) {
                return TEST_UTILITY_TRUE;

            } else if (ReturnValue == TEST_UTILITY_TRUE) {
                return TEST_UTILITY_FALSE;

            } else {
                return ReturnValue;
            }

        } else if (Operator == TestUtilityOpenParentheses) {
            Operator = TestGetOperator(Arguments[3]);
            if (Operator == TestUtilityCloseParentheses) {
                ReturnValue = TestEvaluateExpression(ArgumentCount - 2,
                                                     Arguments + 1);

                return ReturnValue;
            }
        }
    }

    //
    // Allocate the space for a basic parse stack.
    //

    ParseStack = malloc(sizeof(TEST_PARSE_ELEMENT) * ArgumentCount);
    if (ParseStack == NULL) {
        SwPrintError(errno, NULL, "Failed to allocate");
        return TEST_UTILITY_ERROR;
    }

    memset(ParseStack, 0, sizeof(TEST_PARSE_ELEMENT) * ArgumentCount);
    ArgumentIndex = 0;
    StackIndex = 0;
    while (TRUE) {
        if (ArgumentIndex == ArgumentCount) {
            NextElement.Type = TestParseElementEnd;

        } else {

            //
            // Get the next parse stack element.
            //

            Operator = TestGetOperator(Arguments[ArgumentIndex]);
            if (Operator != TestUtilityInvalid) {
                NextElement.Type = TestParseElementOperator;
                NextElement.Operator = Operator;

            } else {
                NextElement.Type = TestParseElementToken;
            }

            NextElement.Token = Arguments[ArgumentIndex];

            //
            // Ensure that if this is a binary operator, there is a token
            // before it.
            //

            if ((Operator != TestUtilityInvalid) &&
                (TestGetOperandCount(Operator) == 2)) {

                if ((StackIndex == 0) ||
                    (ParseStack[StackIndex - 1].Type !=
                     TestParseElementToken)) {

                    SwPrintError(0,
                                 Arguments[ArgumentIndex],
                                 "Binary operator used without left argument");

                    ReturnValue = TEST_UTILITY_ERROR;
                }
            }
        }

        while (TRUE) {
            Result = TestShiftOrReduce(ParseStack,
                                       StackIndex,
                                       &NextElement,
                                       &Shift);

            if (Result == FALSE) {
                ReturnValue = TEST_UTILITY_ERROR;
                goto EvaluateExpressionEnd;
            }

            if (Shift != FALSE) {
                break;
            }

            Result = TestReduce(ParseStack, &StackIndex);
            if (Result == FALSE) {
                ReturnValue = TEST_UTILITY_ERROR;
                goto EvaluateExpressionEnd;
            }

            if ((NextElement.Type == TestParseElementEnd) &&
                (StackIndex == 1) &&
                (ParseStack[0].Type == TestParseElementResult)) {

                ReturnValue = ParseStack[0].Result;
                goto EvaluateExpressionEnd;
            }
        }

        //
        // Shift this next element onto the stack.
        //

        memcpy(&(ParseStack[StackIndex]),
               &NextElement,
               sizeof(TEST_PARSE_ELEMENT));

        StackIndex += 1;
        ArgumentIndex += 1;
    }

EvaluateExpressionEnd:
    if (ParseStack != NULL) {
        free(ParseStack);
    }

    return ReturnValue;
}

BOOL
TestShiftOrReduce (
    PTEST_PARSE_ELEMENT Stack,
    ULONG StackSize,
    PTEST_PARSE_ELEMENT NextElement,
    PBOOL Shift
    )

/*++

Routine Description:

    This routine determines whether the test parser should shift another
    element onto the stack or reduce what it's got.

Arguments:

    Stack - Supplies a pointer to the parse stack.

    StackSize - Supplies the number of elements on the stack.

    NextElement - Supplies a pointer to the next element that would be
        shifted onto the stack.

    Shift - Supplies a pointer where a boolean will be returned indicating
        whether or not to shift (TRUE) or reduce (FALSE).

Return Value:

    TRUE on success.

    FALSE if there was a parsing error.

--*/

{

    TEST_UTILITY_TEST NextOperator;
    TEST_UTILITY_TEST Operator;

    if (StackSize == 0) {
        *Shift = TRUE;
        return TRUE;
    }

    if (NextElement->Type == TestParseElementEnd) {
        *Shift = FALSE;
        return TRUE;
    }

    switch (Stack[StackSize - 1].Type) {
    case TestParseElementOperator:
        Operator = Stack[StackSize - 1].Operator;
        if (Operator == TestUtilityOpenParentheses) {
            *Shift = TRUE;
            return TRUE;
        }

        if (Operator == TestUtilityCloseParentheses) {
            *Shift = FALSE;
            return TRUE;
        }

        //
        // If it's an and, or, or bang, just shift.
        //

        if ((Operator == TestUtilityAnd) || (Operator == TestUtilityOr) ||
            (Operator == TestUtilityBang)) {

            *Shift = TRUE;
            return TRUE;
        }

        //
        // If it's an operator, then all other operators are turned off
        // except for bang.
        //

        if ((NextElement->Type == TestParseElementOperator) &&
            (NextElement->Operator == TestUtilityBang)) {

            *Shift = TRUE;
            return TRUE;
        }

        NextElement->Type = TestParseElementToken;
        *Shift = TRUE;
        return TRUE;

    case TestParseElementToken:

        //
        // If the next thing isn't an operator this is an error.
        //

        if (NextElement->Type != TestParseElementOperator) {
            SwPrintError(0,
                         NULL,
                         "Expected an operator, got %s",
                         NextElement->Token);

            return FALSE;
        }

        //
        // If there's only one element on the stack just shift. That next
        // argument had better be a binary argument.
        //

        NextOperator = NextElement->Operator;
        if (StackSize == 1) {
            if (TestGetOperandCount(NextOperator) != 2) {
                SwPrintError(0, NULL, "Expected a binary operator");
            }

            *Shift = TRUE;
            return TRUE;
        }

        //
        // There's more than one element on the stack, so get the operator
        // back there and compare with this upcoming operator.
        //

        assert(Stack[StackSize - 2].Type == TestParseElementOperator);

        Operator = Stack[StackSize - 2].Operator;
        if (Operator == TestUtilityOpenParentheses) {
            *Shift = TRUE;

        } else if (TestGetOperatorPrecedence(Operator) >=
                   TestGetOperatorPrecedence(NextOperator)) {

            *Shift = FALSE;

        } else {
            *Shift = TRUE;
        }

        return TRUE;

    case TestParseElementResult:

        //
        // If the next thing isn't an operator this is an error.
        //

        if (NextElement->Type != TestParseElementOperator) {
            SwPrintError(0,
                         NULL,
                         "Expected an operator, got %s",
                         NextElement->Token);

            return FALSE;
        }

        //
        // The upcoming operator had better be an and or an or.
        //

        NextOperator = NextElement->Operator;
        if ((NextOperator != TestUtilityAnd) &&
            (NextOperator != TestUtilityOr) &&
            (NextOperator != TestUtilityCloseParentheses)) {

            SwPrintError(0, NULL, "Expected end of expression");
            return FALSE;
        }

        if (StackSize == 1) {
            *Shift = TRUE;
            return TRUE;
        }

        //
        // The operator back there had better be an and, or, or bang.
        //

        Operator = Stack[StackSize - 2].Operator;

        assert((Operator == TestUtilityAnd) || (Operator == TestUtilityOr) ||
               (Operator == TestUtilityBang) ||
               (Operator == TestUtilityOpenParentheses));

        //
        // Figure out which is more important and shift or reduce.
        //

        if (Operator == TestUtilityOpenParentheses) {
            *Shift = TRUE;

        } else if (TestGetOperatorPrecedence(Operator) >=
                   TestGetOperatorPrecedence(NextOperator)) {

            *Shift = FALSE;

        } else {
            *Shift = TRUE;
        }

        return TRUE;

    default:
        break;
    }

    assert(FALSE);

    return FALSE;
}

BOOL
TestReduce (
    PTEST_PARSE_ELEMENT Stack,
    PULONG StackSize
    )

/*++

Routine Description:

    This routine attempts to reduce the stack by performing the topmost
    operation.

Arguments:

    Stack - Supplies a pointer to the parse stack.

    StackSize - Supplies the number of elements on the stack.

Return Value:

    TRUE on success.

    FALSE on failure.

--*/

{

    ULONG OperandCount;
    TEST_UTILITY_TEST Operator;
    BOOL Result;
    INT ReturnValue;

    assert(*StackSize != 0);

    //
    // If there's a single element on the stack or the stack looks like
    // ( something, then just evaluate the top item.
    //

    if ((*StackSize == 1) ||
        ((Stack[*StackSize - 1].Type == TestParseElementToken) &&
         (Stack[*StackSize - 2].Type == TestParseElementOperator) &&
         (Stack[*StackSize - 2].Operator == TestUtilityOpenParentheses))) {

        if (Stack[0].Type != TestParseElementToken) {
            SwPrintError(0, NULL, "Expected token");
            return FALSE;
        }

        TestConvertTokenToResult(&(Stack[0]));
    }

    //
    // If the topmost thing is a close parentheses, reduce until finding the
    // corresponding open parentheses.
    //

    if (Stack[*StackSize - 1].Type == TestParseElementOperator) {
        if (Stack[*StackSize - 1].Operator != TestUtilityCloseParentheses) {
            SwPrintError(0, NULL, "Argument expected");
            return FALSE;
        }

        *StackSize -= 1;
        while (TRUE) {
            if (*StackSize < 2) {
                SwPrintError(0, NULL, "Unexpected close parentheses");
                return FALSE;
            }

            if ((Stack[*StackSize - 1].Type == TestParseElementResult) &&
                (Stack[*StackSize - 2].Type == TestParseElementOperator) &&
                (Stack[*StackSize - 2].Operator ==
                 TestUtilityOpenParentheses)) {

                *StackSize -= 1;
                Stack[*StackSize - 1].Type = TestParseElementResult;
                Stack[*StackSize - 1].Result = Stack[*StackSize].Result;
                return TRUE;
            }

            Result = TestReduce(Stack, StackSize);
            if (Result == FALSE) {
                return FALSE;
            }
        }
    }

    assert(Stack[*StackSize - 2].Type == TestParseElementOperator);

    //
    // Handle the logical operators !, AND, and OR right here.
    //

    Operator = Stack[*StackSize - 2].Operator;
    if (Operator == TestUtilityBang) {

        assert(Stack[*StackSize - 1].Type == TestParseElementResult);

        ReturnValue = Stack[*StackSize - 1].Result;
        if (ReturnValue == TEST_UTILITY_TRUE) {
            ReturnValue = TEST_UTILITY_FALSE;

        } else if (ReturnValue == TEST_UTILITY_FALSE) {
            ReturnValue = TEST_UTILITY_TRUE;
        }

        if (TestDebugPrintEvaluations != FALSE) {
            SwPrintError(0,
                         NULL,
                         "%d <== [!] %d\n",
                         ReturnValue,
                         Stack[*StackSize - 1].Result);
        }

        *StackSize -= 1;
        Stack[*StackSize - 1].Result = ReturnValue;
        Stack[*StackSize - 1].Type = TestParseElementResult;
        return TRUE;

    } else if ((Operator == TestUtilityAnd) || (Operator == TestUtilityOr)) {
        TestConvertTokenToResult(&(Stack[*StackSize - 3]));
        TestConvertTokenToResult(&(Stack[*StackSize - 1]));

        assert(Stack[*StackSize - 3].Type == TestParseElementResult);
        assert(Stack[*StackSize - 1].Type == TestParseElementResult);

        ReturnValue = TestEvaluateAndOr(Operator,
                                        Stack[*StackSize - 3].Result,
                                        Stack[*StackSize - 1].Result);

        *StackSize -= 2;
        Stack[*StackSize - 1].Result = ReturnValue;
        Stack[*StackSize - 1].Type = TestParseElementResult;
        return TRUE;
    }

    //
    // It's not logical, it must be an expression with tokens.
    //

    assert(Stack[*StackSize - 1].Type == TestParseElementToken);

    //
    // Try for a unary operator.
    //

    OperandCount = TestGetOperandCount(Operator);
    if (OperandCount == 1) {
        ReturnValue = TestEvaluateUnaryOperator(Operator,
                                                Stack[*StackSize - 1].Token);

        *StackSize -= 1;

    //
    // Process a binary operator.
    //

    } else {

        assert(OperandCount == 2);

        ReturnValue = TestEvaluateBinaryOperator(Operator,
                                                 Stack[*StackSize - 3].Token,
                                                 Stack[*StackSize - 1].Token);

        *StackSize -= 2;
    }

    Stack[*StackSize - 1].Result = ReturnValue;
    Stack[*StackSize - 1].Type = TestParseElementResult;
    return TRUE;
}

TEST_UTILITY_TEST
TestGetOperator (
    PSTR String
    )

/*++

Routine Description:

    This routine converts the given string into an operator.

Arguments:

    String - Supplies the argument string.

Return Value:

    0 or 1 if the evaluation succeeds.

    >1 on failure.

--*/

{

    if (String[0] == '\0') {
        return TestUtilityInvalid;
    }

    if ((strcmp(String, "=") == 0) || (strcmp(String, "==") == 0)) {
        return TestStringEquals;
    }

    if (strcmp(String, "(") == 0) {
        return TestUtilityOpenParentheses;
    }

    if (strcmp(String, ")") == 0) {
        return TestUtilityCloseParentheses;
    }

    if (strcmp(String, "!") == 0) {
        return TestUtilityBang;
    }

    if (strcmp(String, "!=") == 0) {
        return TestStringNotEquals;
    }

    //
    // All others start with a -.
    //

    if (String[0] != '-') {
        return TestUtilityInvalid;
    }

    String += 1;
    if (strcmp(String, "a") == 0) {
        return TestUtilityAnd;
    }

    if (strcmp(String, "b") == 0) {
        return TestFileIsBlockDevice;
    }

    if (strcmp(String, "c") == 0) {
        return TestFileIsCharacterDevice;
    }

    if (strcmp(String, "d") == 0) {
        return TestFileIsDirectory;
    }

    if (strcmp(String, "e") == 0) {
        return TestFileExists;
    }

    if (strcmp(String, "f") == 0) {
        return TestFileIsRegularFile;
    }

    if (strcmp(String, "g") == 0) {
        return TestFileHasSetGroupId;
    }

    if (strcmp(String, "h") == 0) {
        return TestFileIsSymbolicLink;
    }

    if (strcmp(String, "L") == 0) {
        return TestFileIsSymbolicLink;
    }

    if (strcmp(String, "o") == 0) {
        return TestUtilityOr;
    }

    if (strcmp(String, "p") == 0) {
        return TestFileIsFifo;
    }

    if (strcmp(String, "r") == 0) {
        return TestFileCanRead;
    }

    if (strcmp(String, "S") == 0) {
        return TestFileIsSocket;
    }

    if (strcmp(String, "s") == 0) {
        return TestFileIsNonEmpty;
    }

    if (strcmp(String, "t") == 0) {
        return TestFileDescriptorIsTerminal;
    }

    if (strcmp(String, "u") == 0) {
        return TestFileHasSetUserId;
    }

    if (strcmp(String, "w") == 0) {
        return TestFileCanWrite;
    }

    if (strcmp(String, "x") == 0) {
        return TestFileCanExecute;
    }

    if (strcmp(String, "fe") == 0) {
        return TestFileEqual;
    }

    if (strcmp(String, "nt") == 0) {
        return TestFileNewer;
    }

    if (strcmp(String, "ot") == 0) {
        return TestFileOlder;
    }

    if (strcmp(String, "n") == 0) {
        return TestStringNonZeroLength;
    }

    if (strcmp(String, "z") == 0) {
        return TestStringZeroLength;
    }

    if (strcmp(String, "eq") == 0) {
        return TestIntegerEquals;
    }

    if (strcmp(String, "ne") == 0) {
        return TestIntegerNotEquals;
    }

    if (strcmp(String, "gt") == 0) {
        return TestIntegerGreaterThan;
    }

    if (strcmp(String, "ge") == 0) {
        return TestIntegerGreaterThanOrEqualTo;
    }

    if (strcmp(String, "lt") == 0) {
        return TestIntegerLessThan;
    }

    if (strcmp(String, "le") == 0) {
        return TestIntegerLessThanOrEqualTo;
    }

    return TestUtilityInvalid;
}

ULONG
TestGetOperandCount (
    TEST_UTILITY_TEST Operator
    )

/*++

Routine Description:

    This routine returns the number of operands needed for the given operator.

Arguments:

    Operator - Supplies the operator.

Return Value:

    0 on failure.

    1 or 2 for unary and binary operators.

--*/

{

    if ((Operator > TestIntegerMinValue) && (Operator < TestIntegerMaxValue)) {
        return 2;
    }

    if ((Operator == TestStringEquals) || (Operator == TestStringNotEquals) ||
        (Operator == TestUtilityAnd) || (Operator == TestUtilityOr)) {

        return 2;
    }

    if ((Operator > TestFileMinValue) && (Operator < TestFileMaxValue)) {
        if ((Operator == TestFileEqual) || (Operator == TestFileNewer) ||
            (Operator == TestFileOlder)) {

            return 2;
        }

        return 1;
    }

    if ((Operator == TestStringZeroLength) ||
        (Operator == TestStringNonZeroLength) ||
        (Operator == TestUtilityBang)) {

        return 1;
    }

    return 0;
}

ULONG
TestGetOperatorPrecedence (
    TEST_UTILITY_TEST Operator
    )

/*++

Routine Description:

    This routine returns precedence of the given operator.

Arguments:

    Operator - Supplies the operator.

Return Value:

    Returns the precedence. Higher values are higher precedence.

--*/

{

    if (Operator == TestUtilityCloseParentheses) {
        return 0;
    }

    if (Operator == TestUtilityOr) {
        return 1;
    }

    if (Operator == TestUtilityAnd) {
        return 2;
    }

    if (Operator == TestUtilityBang) {
        return 3;
    }

    if ((Operator == TestStringEquals) || (Operator == TestStringNotEquals)) {
        return 6;
    }

    if (Operator == TestUtilityOpenParentheses) {
        return 7;
    }

    if (TestGetOperandCount(Operator) == 2) {
        return 4;
    }

    return 5;
}

INT
TestEvaluateUnaryOperator (
    TEST_UTILITY_TEST Operator,
    PSTR Operand
    )

/*++

Routine Description:

    This routine evaluates a unary operator.

Arguments:

    Operator - Supplies the operator.

    Operand - Supplies the operand.

Return Value:

    Returns the result of the operation.

--*/

{

    INT Error;
    SWISS_FILE_TEST FileTest;
    INT Result;
    INT ReturnValue;

    if ((Operator > TestFileMinValue) && (Operator < TestFileMaxValue)) {
        switch (Operator) {
        case TestFileIsBlockDevice:
            FileTest = FileTestIsBlockDevice;
            break;

        case TestFileIsCharacterDevice:
            FileTest = FileTestIsCharacterDevice;
            break;

        case TestFileIsDirectory:
            FileTest = FileTestIsDirectory;
            break;

        case TestFileExists:
            FileTest = FileTestExists;
            break;

        case TestFileIsRegularFile:
            FileTest = FileTestIsRegularFile;
            break;

        case TestFileHasSetGroupId:
            FileTest = FileTestHasSetGroupId;
            break;

        case TestFileIsSymbolicLink:
            FileTest = FileTestIsSymbolicLink;
            break;

        case TestFileIsFifo:
            FileTest = FileTestIsFifo;
            break;

        case TestFileCanRead:
            FileTest = FileTestCanRead;
            break;

        case TestFileIsSocket:
            FileTest = FileTestIsSocket;
            break;

        case TestFileIsNonEmpty:
            FileTest = FileTestIsNonEmpty;
            break;

        case TestFileDescriptorIsTerminal:
            FileTest = FileTestDescriptorIsTerminal;
            break;

        case TestFileHasSetUserId:
            FileTest = FileTestHasSetUserId;
            break;

        case TestFileCanWrite:
            FileTest = FileTestCanWrite;
            break;

        case TestFileCanExecute:
            FileTest = FileTestCanExecute;
            break;

        default:

            assert(FALSE);

            return TEST_UTILITY_ERROR;
        }

        ReturnValue = TEST_UTILITY_FALSE;
        Result = SwEvaluateFileTest(FileTest, Operand, &Error);
        if (Error != 0) {
            ReturnValue = TEST_UTILITY_ERROR;

        } else if (Result != FALSE) {
            ReturnValue = TEST_UTILITY_TRUE;
        }

    } else if ((Operator == TestStringZeroLength) ||
               (Operator == TestStringNonZeroLength)) {

        ReturnValue = TestEvaluateStringTest(Operator, Operand, NULL);

    } else {

        assert(FALSE);

        ReturnValue = TEST_UTILITY_ERROR;
    }

    if (TestDebugPrintEvaluations != FALSE) {
        SwPrintError(0,
                     NULL,
                     "%d <== [%s] \"%s\"\n",
                     ReturnValue,
                     TestOperatorStrings[Operator],
                     Operand);
    }

    return ReturnValue;
}

INT
TestEvaluateBinaryOperator (
    TEST_UTILITY_TEST Operator,
    PSTR LeftOperand,
    PSTR RightOperand
    )

/*++

Routine Description:

    This routine evaluates a binary operator.

Arguments:

    Operator - Supplies the operator.

    LeftOperand - Supplies the left operand.

    RightOperand - Supplies the right operand.

Return Value:

    Returns the result of the operation.

--*/

{

    INT LeftResult;
    struct stat LeftStat;
    INT ReturnValue;
    INT RightResult;
    struct stat RightStat;

    if ((Operator > TestFileMinValue) && (Operator < TestFileMaxValue)) {
        memset(&LeftStat, 0, sizeof(struct stat));
        memset(&RightStat, 0, sizeof(struct stat));
        SwStat(LeftOperand, TRUE, &LeftStat);
        SwStat(RightOperand, TRUE, &RightStat);
        ReturnValue = TEST_UTILITY_FALSE;
        switch (Operator) {
        case TestFileEqual:
            if ((LeftStat.st_dev == RightStat.st_dev) &&
                (LeftStat.st_ino == RightStat.st_ino)) {

                ReturnValue = TEST_UTILITY_TRUE;
            }

            break;

        case TestFileNewer:
            if (LeftStat.st_mtime > RightStat.st_mtime) {
                ReturnValue = TEST_UTILITY_TRUE;
            }

            break;

        case TestFileOlder:
            if (LeftStat.st_mtime < RightStat.st_mtime) {
                ReturnValue = TEST_UTILITY_TRUE;
            }

            break;

        default:

            assert(FALSE);

            ReturnValue = TEST_UTILITY_ERROR;
            break;
        }

    } else if ((Operator > TestStringMinValue) &&
               (Operator < TestStringMaxValue)) {

        ReturnValue = TestEvaluateStringTest(Operator,
                                             LeftOperand,
                                             RightOperand);

    } else if ((Operator > TestIntegerMinValue) &&
               (Operator < TestIntegerMaxValue)) {

        ReturnValue = TestEvaluateIntegerTest(Operator,
                                              LeftOperand,
                                              RightOperand);

    } else if ((Operator == TestUtilityAnd) || (Operator == TestUtilityOr)) {
        LeftResult = TEST_UTILITY_FALSE;
        RightResult = TEST_UTILITY_FALSE;
        if (strlen(LeftOperand) != 0) {
            LeftResult = TEST_UTILITY_TRUE;
        }

        if (strlen(RightOperand) != 0) {
            RightResult = TEST_UTILITY_TRUE;
        }

        ReturnValue = TestEvaluateAndOr(Operator, LeftResult, RightResult);

    } else {

        assert(FALSE);

        ReturnValue = TEST_UTILITY_ERROR;
    }

    if (TestDebugPrintEvaluations != FALSE) {
        SwPrintError(0,
                     NULL,
                     "%d <== \"%s\" [%s] \"%s\"\n",
                     ReturnValue,
                     LeftOperand,
                     TestOperatorStrings[Operator],
                     RightOperand);
    }

    return ReturnValue;
}

INT
TestEvaluateStringTest (
    TEST_UTILITY_TEST Operator,
    PSTR String1,
    PSTR String2
    )

/*++

Routine Description:

    This routine evaluates a string test.

Arguments:

    Operator - Supplies the operator.

    String1 - Supplies a pointer to the first string operand.

    String2 - Supplies a pointer to the second string operand. Not all
        operators require a second operand.

Return Value:

    Returns the result of the operation.

--*/

{

    INT ReturnValue;

    ReturnValue = TEST_UTILITY_FALSE;
    switch (Operator) {
    case TestStringNonZeroLength:
        if (strlen(String1) != 0) {
            ReturnValue = TEST_UTILITY_TRUE;
        }

        break;

    case TestStringZeroLength:
        if (strlen(String1) == 0) {
            ReturnValue = TEST_UTILITY_TRUE;
        }

        break;

    case TestStringEquals:

        assert((String1 != NULL) && (String2 != NULL));

        if (strcmp(String1, String2) == 0) {
            ReturnValue = TEST_UTILITY_TRUE;
        }

        break;

    case TestStringNotEquals:

        assert((String1 != NULL) && (String2 != NULL));

        if (strcmp(String1, String2) != 0) {
            ReturnValue = TEST_UTILITY_TRUE;
        }

        break;

    default:

        assert(FALSE);

        break;
    }

    return ReturnValue;
}

INT
TestEvaluateIntegerTest (
    TEST_UTILITY_TEST Operator,
    PSTR LeftIntegerString,
    PSTR RightIntegerString
    )

/*++

Routine Description:

    This routine evaluates an integer test.

Arguments:

    Operator - Supplies the operator.

    LeftIntegerString - Supplies a pointer to the string containing the left
        numeric value.

    RightIntegerString - Supplies a pointer to the string containing the right
        numeric value.

Return Value:

    Returns the result of the operation.

--*/

{

    PSTR AfterScan;
    INT Left;
    INT ReturnValue;
    INT Right;

    //
    // Convert the strings to integers.
    //

    Left = strtol(LeftIntegerString, &AfterScan, 10);
    if ((Left == 0) && (AfterScan == LeftIntegerString)) {
        SwPrintError(0, NULL, "%s: Invalid integer", LeftIntegerString);
        return TEST_UTILITY_ERROR;
    }

    Right = strtol(RightIntegerString, &AfterScan, 10);
    if ((Right == 0) && (AfterScan == RightIntegerString)) {
        SwPrintError(0, NULL, "%s: Invalid integer", RightIntegerString);
        return TEST_UTILITY_ERROR;
    }

    ReturnValue = TEST_UTILITY_FALSE;
    switch (Operator) {
    case TestIntegerEquals:
        if (Left == Right) {
            ReturnValue = TEST_UTILITY_TRUE;
        }

        break;

    case TestIntegerNotEquals:
        if (Left != Right) {
            ReturnValue = TEST_UTILITY_TRUE;
        }

        break;

    case TestIntegerGreaterThan:
        if (Left > Right) {
            ReturnValue = TEST_UTILITY_TRUE;
        }

        break;

    case TestIntegerGreaterThanOrEqualTo:
        if (Left >= Right) {
            ReturnValue = TEST_UTILITY_TRUE;
        }

        break;

    case TestIntegerLessThan:
        if (Left < Right) {
            ReturnValue = TEST_UTILITY_TRUE;
        }

        break;

    case TestIntegerLessThanOrEqualTo:
        if (Left <= Right) {
            ReturnValue = TEST_UTILITY_TRUE;
        }

        break;

    default:

        assert(FALSE);

        break;
    }

    return ReturnValue;
}

INT
TestEvaluateAndOr (
    TEST_UTILITY_TEST Operator,
    INT Left,
    INT Right
    )

/*++

Routine Description:

    This routine evaluates and AND (-a) or and OR (-o) operator.

Arguments:

    Operator - Supplies the operator, which should be AND or OR.

    Left - Supplies the left result, a TEST_UTILITY_* value.

    Right - Supplies the right result, a TEST_UTILITY_* value.

Return Value:

    Returns the TEST_UTILITY_* result.

--*/

{

    INT ReturnValue;

    assert((Operator == TestUtilityAnd) || (Operator == TestUtilityOr));

    ReturnValue = TEST_UTILITY_FALSE;
    if (Operator == TestUtilityAnd) {
        if ((Left == TEST_UTILITY_TRUE) && (Right == TEST_UTILITY_TRUE)) {
            ReturnValue = TEST_UTILITY_TRUE;
        }

    } else {
        if ((Left == TEST_UTILITY_TRUE) || (Right == TEST_UTILITY_TRUE)) {
            ReturnValue = TEST_UTILITY_TRUE;
        }
    }

    if (TestDebugPrintEvaluations != FALSE) {
        SwPrintError(0,
                     NULL,
                     "%d <== %d [%s] %d\n",
                     ReturnValue,
                     Left,
                     TestOperatorStrings[Operator],
                     Right);
    }

    return ReturnValue;
}

VOID
TestConvertTokenToResult (
    PTEST_PARSE_ELEMENT Element
    )

/*++

Routine Description:

    This routine converts a token type parse element into a result element,
    which is non-zero if the given token has a non-zero length.

Arguments:

    Element - Supplies a pointer to the token element to convert into a result.

Return Value:

    None.

--*/

{

    //
    // Skip it if it's already a result.
    //

    if (Element->Type == TestParseElementResult) {
        return;
    }

    //
    // Only tokens are handled here, there's no converting an operator to a
    // result.
    //

    assert(Element->Type == TestParseElementToken);

    if (strlen(Element->Token) != 0) {
        Element->Result = TEST_UTILITY_TRUE;

    } else {
        Element->Result = TEST_UTILITY_FALSE;
    }

    Element->Type = TestParseElementResult;
    return;
}

