/*++

Copyright (c) 2013 Minoca Corp. All Rights Reserved

Module Name:

    parser.c

Abstract:

    This module implements the shell grammar parser.

Author:

    Evan Green 5-Jun-2013

Environment:

    User Mode

--*/

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

#include "sh.h"
#include "shparse.h"
#include "../swlib.h"
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>

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

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

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

BOOL
ShParseCompleteCommand (
    PSHELL Shell,
    PSHELL_NODE *CompleteCommandNode
    );

PSHELL_NODE
ShParseList (
    PSHELL Shell
    );

PSHELL_NODE
ShParseAndOr (
    PSHELL Shell
    );

PSHELL_NODE
ShParsePipeline (
    PSHELL Shell
    );

PSHELL_NODE
ShParseCommand (
    PSHELL Shell
    );

PSHELL_NODE
ShParseCompoundCommand (
    PSHELL Shell
    );

PSHELL_NODE
ShParseBraceGroup (
    PSHELL Shell
    );

PSHELL_NODE
ShParseSubshell (
    PSHELL Shell
    );

PSHELL_NODE
ShParseIf (
    PSHELL Shell
    );

PSHELL_NODE
ShParseWhileOrUntil (
    PSHELL Shell
    );

PSHELL_NODE
ShParseFor (
    PSHELL Shell
    );

PSHELL_NODE
ShParseCase (
    PSHELL Shell
    );

BOOL
ShParsePattern (
    PSHELL Shell,
    PSHELL_NODE Case,
    PSHELL_CASE_PATTERN_SET *NewPatternSet
    );

PSHELL_NODE
ShParseDoGroup (
    PSHELL Shell
    );

PSHELL_NODE
ShParseCompoundList (
    PSHELL Shell
    );

PSHELL_NODE
ShParseTerm (
    PSHELL Shell
    );

PSHELL_NODE
ShParseSimpleCommandOrFunction (
    PSHELL Shell
    );

PSHELL_NODE
ShParseSimpleCommand (
    PSHELL Shell,
    PSTR FirstWord,
    UINTN FirstWordSize
    );

PSHELL_NODE
ShParseFunctionDefinition (
    PSHELL Shell,
    PSTR FunctionName,
    UINTN FunctionNameSize
    );

BOOL
ShParseOptionalRedirectList (
    PSHELL Shell,
    PSHELL_NODE Node
    );

BOOL
ShParseRedirection (
    PSHELL Shell,
    PSHELL_NODE Node
    );

BOOL
ShParseAssignment (
    PSHELL Shell,
    PSHELL_NODE Node
    );

BOOL
ShParseLineBreak (
    PSHELL Shell,
    BOOL Required,
    BOOL FirstCommandWord
    );

BOOL
ShParseSeparator (
    PSHELL Shell,
    PCHAR Separator
    );

BOOL
ShParseSeparatorOp (
    PSHELL Shell,
    PCHAR Separator
    );

PSHELL_NODE
ShCreateNode (
    PSHELL Shell,
    SHELL_NODE_TYPE Type
    );

VOID
ShDeleteNode (
    PSHELL_NODE Node
    );

VOID
ShPrintNode (
    PSHELL Shell,
    PSHELL_NODE Node,
    ULONG Depth
    );

BOOL
ShCreateRedirection (
    PSHELL Shell,
    PSHELL_NODE Node,
    SHELL_IO_REDIRECTION_TYPE Type,
    INT FileNumber,
    PSTR FileName,
    UINTN FileNameSize
    );

VOID
ShDestroyRedirection (
    PSHELL_IO_REDIRECT Redirect
    );

BOOL
ShCreateAssignment (
    PSHELL_NODE Node,
    PSTR Name,
    UINTN NameSize,
    PSTR Value,
    UINTN ValueSize
    );

VOID
ShDestroyAssignment (
    PSHELL_ASSIGNMENT Assignment
    );

BOOL
ShAddPatternToSet (
    PSHELL_CASE_PATTERN_SET Set,
    PSTR Pattern,
    UINTN PatternSize
    );

VOID
ShDestroyCasePatternList (
    PSHELL_NODE CaseNode
    );

BOOL
ShAddComponentToCommand (
    PSHELL_NODE Command,
    PSTR Component,
    UINTN ComponentSize
    );

BOOL
ShIsStringQuoted (
    PSTR String
    );

VOID
ShParseError (
    PSHELL Shell,
    PSHELL_NODE Node,
    PSTR Format,
    ...
    );

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

BOOL ShDebugPrintParseTree = FALSE;

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

BOOL
ShParse (
    PSHELL Shell,
    PSHELL_NODE *Node
    )

/*++

Routine Description:

    This routine attempts to parse a complete command out of the shell input.

Arguments:

    Shell - Supplies a pointer to the shell object.

    Node - Supplies a pointer where the parsed node representing the command
        will be returned.

Return Value:

    TRUE on success.

    FALSE on failure.

--*/

{

    PSHELL_NODE Command;
    BOOL Result;

    Command = NULL;

    //
    // Prime the lexer.
    //

    if (Shell->Lexer.LexerPrimed == FALSE) {
        Result = ShGetToken(Shell, TRUE);
        if (Result == FALSE) {
            goto ParseEnd;
        }

        Shell->Lexer.LexerPrimed = TRUE;
    }

    Result = ShParseCompleteCommand(Shell, &Command);
    if ((Result == FALSE) &&
        ((Shell->Options & SHELL_OPTION_INTERACTIVE) == 0)) {

        PRINT_ERROR("sh: Failed to parse command.\n");
    }

    if ((Command != NULL) && (ShDebugPrintParseTree != FALSE)) {
        ShPrintNode(Shell, Command, 0);
    }

ParseEnd:
    *Node = Command;
    return Result;
}

VOID
ShDestroyHereDocument (
    PSHELL_HERE_DOCUMENT HereDocument
    )

/*++

Routine Description:

    This routine destroys a here document. It assumes the here document is
    already removed from any list it was on.

Arguments:

    HereDocument - Supplies a pointer to the here document to destroy.

Return Value:

    None.

--*/

{

    if (HereDocument->EndWord != NULL) {
        free(HereDocument->EndWord);
    }

    if (HereDocument->Document != NULL) {
        free(HereDocument->Document);
    }

    free(HereDocument);
    return;
}

VOID
ShRetainNode (
    PSHELL_NODE Node
    )

/*++

Routine Description:

    This routine increases the reference count on a node.

Arguments:

    Node - Supplies a pointer to the node to retain.

Return Value:

    None.

--*/

{

    assert((Node->ReferenceCount > 0) && (Node->ReferenceCount < 0x1000));

    Node->ReferenceCount += 1;
    return;
}

VOID
ShReleaseNode (
    PSHELL_NODE Node
    )

/*++

Routine Description:

    This routine releases a reference on a shell node. If this causes the
    reference count to drop to zero then the node is destroyed.

Arguments:

    Node - Supplies a pointer to the node to release.

Return Value:

    None.

--*/

{

    assert((Node->ReferenceCount > 0) && (Node->ReferenceCount < 0x1000));

    Node->ReferenceCount -= 1;
    if (Node->ReferenceCount == 0) {
        ShDeleteNode(Node);
    }

    return;
}

BOOL
ShIsName (
    PSTR String,
    UINTN StringSize
    )

/*++

Routine Description:

    This routine determines if the given string is a valid NAME according to
    the shell grammar.

Arguments:

    String - Supplies a pointer to the candidate string.

    StringSize - Supplies the number of characters to check.

Return Value:

    TRUE if the string is a valid name.

    FALSE if the string is not a valid name.

--*/

{

    UCHAR Character;
    ULONG Index;

    if (StringSize == 0) {
        return FALSE;
    }

    //
    // The first character can be A through Z or an underscore.
    //

    Character = *String;
    if (!SHELL_NAME_FIRST_CHARACTER(Character)) {
        return FALSE;
    }

    for (Index = 1; Index < StringSize; Index += 1) {
        Character = String[Index];
        if (Character == '\0') {
            break;
        }

        if (!SHELL_NAME_CHARACTER(Character)) {
            return FALSE;
        }
    }

    return TRUE;
}

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

BOOL
ShParseCompleteCommand (
    PSHELL Shell,
    PSHELL_NODE *CompleteCommandNode
    )

/*++

Routine Description:

    This routine attempts to parse a complete command out of the shell input.

Arguments:

    Shell - Supplies a pointer to the shell object.

    CompleteCommandNode - Supplies a pointer where the complete command will
        be returned on success. The caller is responsible for destroying this
        node.

Return Value:

    TRUE on success.

    FALSE on failure.

--*/

{

    PSHELL_NODE List;
    BOOL Result;
    CHAR SeparatorOp;

    Result = FALSE;
    List = NULL;

    //
    // Get rid of any extraneous newlines.
    //

    while (Shell->Lexer.TokenType == '\n') {
        ShCheckForSignals(Shell);
        ShPrintPrompt(Shell, 1);
        Result = ShGetToken(Shell, TRUE);
        if (Result == FALSE) {
            goto ParseCompleteCommandEnd;
        }
    }

    if (Shell->Lexer.TokenType == TOKEN_END_OF_FILE) {
        Result = TRUE;
        goto ParseCompleteCommandEnd;
    }

    List = ShParseList(Shell);
    if (List == NULL) {
        Result = FALSE;
        goto ParseCompleteCommandEnd;
    }

    Result = ShParseSeparatorOp(Shell, &SeparatorOp);
    if ((Result != FALSE) && (SeparatorOp == '&')) {
        List->RunInBackground = TRUE;
    }

    Result = TRUE;

ParseCompleteCommandEnd:
    if (Result == FALSE) {
        if (List != NULL) {
            ShReleaseNode(List);
            List = NULL;
        }
    }

    *CompleteCommandNode = List;
    return Result;
}

PSHELL_NODE
ShParseList (
    PSHELL Shell
    )

/*++

Routine Description:

    This routine attempts to parse a list out of the shell input.

Arguments:

    Shell - Supplies a pointer to the shell object.

Return Value:

    Returns a pointer to the node on success.

    NULL on failure.

--*/

{

    PSHELL_NODE AndOr;
    PSHELL_NODE List;
    BOOL Result;
    CHAR SeparatorOp;

    Result = FALSE;
    List = NULL;

    //
    // Parse and-or statements separated by separator operators.
    //

    while (TRUE) {
        AndOr = ShParseAndOr(Shell);
        if (AndOr == NULL) {
            if (List == NULL) {
                Result = FALSE;
                ShParseError(Shell, NULL, "Unexpected token.");
                goto ParseListEnd;
            }

            break;
        }

        Result = ShParseSeparatorOp(Shell, &SeparatorOp);

        //
        // If this is the first time around and there's only one item, then
        // just return that item.
        //

        if ((Result == FALSE) && (List == NULL)) {
            List = AndOr;
            break;
        }

        //
        // There's more than one and-or. If the listhas yet to be made, make it
        // now.
        //

        if (List == NULL) {
            List = ShCreateNode(Shell, ShellNodeList);
            if (List == NULL) {
                goto ParseListEnd;
            }
        }

        INSERT_BEFORE(&(AndOr->SiblingListEntry), &(List->Children));

        //
        // Regardless of where the command was, if there's no separator, this
        // loop is done.
        //

        if (Result == FALSE) {
            break;
        }

        if (SeparatorOp == '&') {
            AndOr->RunInBackground = TRUE;
        }
    }

    AndOr = NULL;
    Result = TRUE;

ParseListEnd:
    if (AndOr != NULL) {
        ShReleaseNode(AndOr);
    }

    if (Result == FALSE) {
        if (List != NULL) {
            ShReleaseNode(List);
            List = NULL;
        }
    }

    return List;
}

PSHELL_NODE
ShParseAndOr (
    PSHELL Shell
    )

/*++

Routine Description:

    This routine attempts to parse an and-or statement out of the shell input.

Arguments:

    Shell - Supplies a pointer to the shell object.

Return Value:

    Returns a pointer to the node on success.

    NULL on failure.

--*/

{

    PSHELL_NODE AndOr;
    PSHELL_NODE Pipeline;
    BOOL Result;
    ULONG SeparatorToken;

    Result = FALSE;
    AndOr = NULL;

    //
    // Parse pipeline statements separated by separator operators.
    //

    while (TRUE) {
        Pipeline = ShParsePipeline(Shell);
        if (Pipeline == NULL) {
            goto ParseAndOrEnd;
        }

        SeparatorToken = Shell->Lexer.TokenType;

        //
        // If this is the first time around and there's only one item, then
        // just return that item.
        //

        if ((SeparatorToken != TOKEN_DOUBLE_AND) &&
            (SeparatorToken != TOKEN_DOUBLE_OR) &&
            (AndOr == NULL)) {

            AndOr = Pipeline;
            break;
        }

        //
        // There's more than one pipeline. If the and-or has yet to be
        // made, make it now.
        //

        if (AndOr == NULL) {
            AndOr = ShCreateNode(Shell, ShellNodeAndOr);
            if (AndOr == NULL) {
                goto ParseAndOrEnd;
            }
        }

        INSERT_BEFORE(&(Pipeline->SiblingListEntry), &(AndOr->Children));

        //
        // Regardless of where the pipeline was, if there's no valid AND or OR,
        // stop.
        //

        if ((SeparatorToken != TOKEN_DOUBLE_AND) &&
            (SeparatorToken != TOKEN_DOUBLE_OR)) {

            break;
        }

        Pipeline->AndOr = SeparatorToken;
        Pipeline = NULL;
        Result = ShGetToken(Shell, TRUE);
        if (Result == FALSE) {
            goto ParseAndOrEnd;
        }

        Result = ShParseLineBreak(Shell, FALSE, TRUE);
        if (Result == FALSE) {
            goto ParseAndOrEnd;
        }
    }

    Pipeline = NULL;
    Result = TRUE;

ParseAndOrEnd:
    if (Pipeline != NULL) {
        ShReleaseNode(Pipeline);
    }

    if (Result == FALSE) {
        if (AndOr != NULL) {
            ShReleaseNode(AndOr);
            AndOr = NULL;
        }
    }

    return AndOr;
}

PSHELL_NODE
ShParsePipeline (
    PSHELL Shell
    )

/*++

Routine Description:

    This routine attempts to parse a pipeline out of the shell input.

Arguments:

    Shell - Supplies a pointer to the shell object.

Return Value:

    Returns a pointer to the node on success.

    NULL on failure.

--*/

{

    PSHELL_NODE Command;
    PSHELL_NODE Pipeline;
    BOOL Result;

    Command = NULL;
    Pipeline = NULL;
    Result = FALSE;

    //
    // Parse an optional ! at the beginning.
    //

    if (Shell->Lexer.TokenType == '!') {
        Pipeline = ShCreateNode(Shell, ShellNodePipeline);
        if (Pipeline == NULL) {
            goto ParsePipelineEnd;
        }

        Pipeline->U.Pipeline.Bang = TRUE;
        Result = ShGetToken(Shell, TRUE);
        if (Result == FALSE) {
            goto ParsePipelineEnd;
        }
    }

    //
    // Parse pipeline statements separated by separator operators.
    //

    while (TRUE) {
        Command = ShParseCommand(Shell);
        if (Command == NULL) {
            Result = FALSE;
            goto ParsePipelineEnd;
        }

        //
        // If this is the first time around and there's only one item, then
        // just return that item.
        //

        if ((Shell->Lexer.TokenType != '|') && (Pipeline == NULL)) {
            Pipeline = Command;
            break;
        }

        //
        // There's more than one command. If the pipeline has yet to be made,
        // make it now.
        //

        if (Pipeline == NULL) {
            Pipeline = ShCreateNode(Shell, ShellNodePipeline);
            if (Pipeline == NULL) {
                goto ParsePipelineEnd;
            }
        }

        INSERT_BEFORE(&(Command->SiblingListEntry), &(Pipeline->Children));

        //
        // Regardless of where the command was, if there's no valid pipe
        // character then stop. Otherwise, move over the pipe character.
        //

        if (Shell->Lexer.TokenType != '|') {
            break;
        }

        Result = ShGetToken(Shell, TRUE);
        if (Result == FALSE) {
            Command = NULL;
            goto ParsePipelineEnd;
        }

        //
        // It's known now that most recent command wasn't the last, so set it
        // to run in the background so the shell doesn't wait on it.
        //

        Command->RunInBackground = TRUE;
        ShParseLineBreak(Shell, FALSE, TRUE);
    }

    Command = NULL;
    Result = TRUE;

ParsePipelineEnd:
    if (Command != NULL) {
        ShReleaseNode(Command);
    }

    if (Result == FALSE) {
        if (Pipeline != NULL) {
            ShReleaseNode(Pipeline);
            Pipeline = NULL;
        }
    }

    return Pipeline;
}

PSHELL_NODE
ShParseCommand (
    PSHELL Shell
    )

/*++

Routine Description:

    This routine attempts to parse a command.

Arguments:

    Shell - Supplies a pointer to the shell object.

Return Value:

    Returns a pointer to the node on success.

    NULL on failure.

--*/

{

    PSHELL_NODE Command;

    //
    // A command is either a simple command, compound command, or function
    // definition.
    //

    switch (Shell->Lexer.TokenType) {
    case '{':
    case '(':
    case TOKEN_FOR:
    case TOKEN_CASE:
    case TOKEN_IF:
    case TOKEN_WHILE:
    case TOKEN_UNTIL:
        Command = ShParseCompoundCommand(Shell);
        break;

    case TOKEN_ASSIGNMENT_WORD:
    case TOKEN_IO_NUMBER:
    case TOKEN_LESS_THAN_AND:
    case TOKEN_GREATER_THAN_AND:
    case TOKEN_DOUBLE_GREATER_THAN:
    case TOKEN_DOUBLE_LESS_THAN:
    case TOKEN_DOUBLE_LESS_THAN_DASH:
    case '>':
    case '<':
        Command = ShParseSimpleCommand(Shell, NULL, 0);
        break;

    case TOKEN_WORD:
        Command = ShParseSimpleCommandOrFunction(Shell);
        break;

    default:
        Command = NULL;
        break;
    }

    return Command;
}

PSHELL_NODE
ShParseCompoundCommand (
    PSHELL Shell
    )

/*++

Routine Description:

    This routine attempts to parse a command.

Arguments:

    Shell - Supplies a pointer to the shell object.

Return Value:

    Returns a pointer to the node on success.

    NULL on failure.

--*/

{

    PSHELL_NODE Command;
    BOOL Result;

    Command = NULL;
    Result = TRUE;

    //
    // A command is either a simple command, compound command, or function
    // definition.
    //

    switch (Shell->Lexer.TokenType) {
    case '{':
        Command = ShParseBraceGroup(Shell);
        break;

    case '(':
        Command = ShParseSubshell(Shell);
        break;

    case TOKEN_FOR:
        Command = ShParseFor(Shell);
        break;

    case TOKEN_CASE:
        Command = ShParseCase(Shell);
        break;

    case TOKEN_IF:
        Command = ShParseIf(Shell);
        break;

    case TOKEN_WHILE:
    case TOKEN_UNTIL:
        Command = ShParseWhileOrUntil(Shell);
        break;

    default:
        ShParseError(Shell, NULL, "Unexpected token for compound command.");
        break;
    }

    if (Command == NULL) {
        return NULL;
    }

    //
    // Compound commands can optionally have a redirect list on them too.
    //

    Result = ShParseOptionalRedirectList(Shell, Command);
    if (Result == FALSE) {
        goto ParseCompoundCommandEnd;
    }

ParseCompoundCommandEnd:
    if (Result == FALSE) {
        if (Command != NULL) {
            ShReleaseNode(Command);
            Command = NULL;
        }
    }

    return Command;
}

PSHELL_NODE
ShParseBraceGroup (
    PSHELL Shell
    )

/*++

Routine Description:

    This routine attempts to parse a set of statements surrounded by a brace
    group.

Arguments:

    Shell - Supplies a pointer to the shell object.

Return Value:

    Returns a pointer to the node on success.

    NULL on failure.

--*/

{

    PSHELL_NODE BraceGroup;
    PSHELL_NODE CompoundList;
    BOOL Result;

    //
    // Scan over the open brace.
    //

    assert(Shell->Lexer.TokenType == '{');

    BraceGroup = ShCreateNode(Shell, ShellNodeBraceGroup);
    if (BraceGroup == NULL) {
        Result = FALSE;
        goto ParseBraceGroupEnd;
    }

    Result = ShGetToken(Shell, TRUE);
    if (Result == FALSE) {
        goto ParseBraceGroupEnd;
    }

    //
    // Get the tender compound list inside.
    //

    CompoundList = ShParseCompoundList(Shell);
    if (CompoundList == NULL) {
        Result = FALSE;
        goto ParseBraceGroupEnd;
    }

    INSERT_BEFORE(&(CompoundList->SiblingListEntry), &(BraceGroup->Children));

    //
    // Scan over the closing brace.
    //

    if (Shell->Lexer.TokenType != '}') {
        ShParseError(Shell,
                     NULL,
                     "Expected '}' for open brace at line %d.",
                     BraceGroup->LineNumber);

        Result = FALSE;
        goto ParseBraceGroupEnd;
    }

    Result = ShGetToken(Shell, TRUE);
    if (Result == FALSE) {
        goto ParseBraceGroupEnd;
    }

ParseBraceGroupEnd:
    if (Result == FALSE) {
        if (BraceGroup != NULL) {
            ShReleaseNode(BraceGroup);
            BraceGroup = NULL;
        }
    }

    return BraceGroup;
}

PSHELL_NODE
ShParseSubshell (
    PSHELL Shell
    )

/*++

Routine Description:

    This routine attempts to parse a subshell sequence, which is basically of
    the form ( compound list ).

Arguments:

    Shell - Supplies a pointer to the shell object.

Return Value:

    Returns a pointer to the node on success.

    NULL on failure.

--*/

{

    PSHELL_NODE CompoundList;
    PSHELL_NODE Node;
    BOOL Result;

    Node = ShCreateNode(Shell, ShellNodeSubshell);
    if (Node == NULL) {
        Result = FALSE;
        goto ParseSubshellEnd;
    }

    assert(Shell->Lexer.TokenType == '(');

    Result = ShGetToken(Shell, TRUE);
    if (Result == FALSE) {
        goto ParseSubshellEnd;
    }

    CompoundList = ShParseCompoundList(Shell);
    if (CompoundList == NULL) {
        Result = FALSE;
        goto ParseSubshellEnd;
    }

    INSERT_BEFORE(&(CompoundList->SiblingListEntry), &(Node->Children));
    if (Shell->Lexer.TokenType != ')') {
        ShParseError(Shell,
                     NULL,
                     "Expected ')' for subshell at line %d.",
                     Node->LineNumber);

        Result = FALSE;
        goto ParseSubshellEnd;
    }

    Result = ShGetToken(Shell, TRUE);
    if (Result == FALSE) {
        goto ParseSubshellEnd;
    }

    Result = TRUE;

ParseSubshellEnd:
    if (Result == FALSE) {
        if (Node != NULL) {
            ShReleaseNode(Node);
            Node = NULL;
        }
    }

    return Node;
}

PSHELL_NODE
ShParseIf (
    PSHELL Shell
    )

/*++

Routine Description:

    This routine attempts to parse an if statement.

Arguments:

    Shell - Supplies a pointer to the shell object.

Return Value:

    Returns a pointer to the node on success.

    NULL on failure.

--*/

{

    PSHELL_NODE Condition;
    PSHELL_NODE ElseBody;
    PSHELL_NODE Node;
    BOOL Result;
    BOOL SwallowFi;
    PSHELL_NODE ThenCompoundList;

    assert((Shell->Lexer.TokenType == TOKEN_IF) ||
           (Shell->Lexer.TokenType == TOKEN_ELIF));

    //
    // Don't swallow the "fi" if this is an elif.
    //

    SwallowFi = TRUE;
    if (Shell->Lexer.TokenType == TOKEN_ELIF) {
        SwallowFi = FALSE;
    }

    Node = NULL;
    Result = ShGetToken(Shell, TRUE);
    if (Result == FALSE) {
        goto ParseIfEnd;
    }

    Node = ShCreateNode(Shell, ShellNodeIf);
    if (Node == NULL) {
        Result = FALSE;
        goto ParseIfEnd;
    }

    //
    // Parse out the condition part of the if statement.
    //

    Condition = ShParseCompoundList(Shell);
    if (Condition == NULL) {
        Result = FALSE;
        goto ParseIfEnd;
    }

    INSERT_BEFORE(&(Condition->SiblingListEntry), &(Node->Children));

    //
    // The next thing had better be a "then".
    //

    if (Shell->Lexer.TokenType != TOKEN_THEN) {
        ShParseError(Shell,
                     NULL,
                     "Expected 'then' for if at line %d.",
                     Node->LineNumber);

        Result = FALSE;
        goto ParseIfEnd;
    }

    Result = ShGetToken(Shell, TRUE);
    if (Result == FALSE) {
        goto ParseIfEnd;
    }

    //
    // Parse the compound list of stuff to do if the condition succeeds.
    //

    ThenCompoundList = ShParseCompoundList(Shell);
    if (ThenCompoundList == NULL) {
        Result = FALSE;
        goto ParseIfEnd;
    }

    INSERT_BEFORE(&(ThenCompoundList->SiblingListEntry), &(Node->Children));

    //
    // Ok, so there's an else or elif. If it's an else, parse out the
    // compound list that follows. For elifs, pretend it's a brand new if
    // statement inside the else.
    //

    if ((Shell->Lexer.TokenType == TOKEN_ELSE) ||
        (Shell->Lexer.TokenType == TOKEN_ELIF)) {

        if (Shell->Lexer.TokenType == TOKEN_ELSE) {
            Result = ShGetToken(Shell, TRUE);
            if (Result == FALSE) {
                goto ParseIfEnd;
            }

            ElseBody = ShParseCompoundList(Shell);

        } else {
            ElseBody = ShParseIf(Shell);
        }

        if (ElseBody == NULL) {
            Result = FALSE;
            goto ParseIfEnd;
        }

        INSERT_BEFORE(&(ElseBody->SiblingListEntry), &(Node->Children));
    }

    //
    // There had better be a fi at the end.
    //

    if (Shell->Lexer.TokenType != TOKEN_FI) {
        ShParseError(Shell,
                     NULL,
                     "Expected 'fi' for if at line %d.",
                     Node->LineNumber);

        Result = FALSE;
        goto ParseIfEnd;
    }

    if (SwallowFi != FALSE) {
        Result = ShGetToken(Shell, TRUE);
        if (Result == FALSE) {
            goto ParseIfEnd;
        }
    }

ParseIfEnd:
    if (Result == FALSE) {
        if (Node != NULL) {
            ShReleaseNode(Node);
        }

        Node = NULL;
    }

    return Node;
}

PSHELL_NODE
ShParseWhileOrUntil (
    PSHELL Shell
    )

/*++

Routine Description:

    This routine attempts to parse either a while statement or an until
    statement.

Arguments:

    Shell - Supplies a pointer to the shell object.

Return Value:

    Returns a pointer to the node on success.

    NULL on failure.

--*/

{

    PSHELL_NODE Condition;
    PSHELL_NODE DoGroup;
    PSHELL_NODE Node;
    SHELL_NODE_TYPE NodeType;
    BOOL Result;

    assert((Shell->Lexer.TokenType == TOKEN_WHILE) ||
           (Shell->Lexer.TokenType == TOKEN_UNTIL));

    if (Shell->Lexer.TokenType == TOKEN_WHILE) {
        NodeType = ShellNodeWhile;

    } else {
        NodeType = ShellNodeUntil;
    }

    Node = ShCreateNode(Shell, NodeType);
    if (Node == NULL) {
        Result = FALSE;
        goto ParseWhileOrUntilEnd;
    }

    //
    // Skip over the while or until.
    //

    Result = ShGetToken(Shell, TRUE);
    if (Result == FALSE) {
        goto ParseWhileOrUntilEnd;
    }

    //
    // Parse the compound list that is the condition.
    //

    Condition = ShParseCompoundList(Shell);
    if (Condition == NULL) {
        Result = FALSE;
        goto ParseWhileOrUntilEnd;
    }

    INSERT_BEFORE(&(Condition->SiblingListEntry), &(Node->Children));

    //
    // Parse the do group of stuff to do inside the loop.
    //

    DoGroup = ShParseDoGroup(Shell);
    if (DoGroup == NULL) {
        Result = FALSE;
        goto ParseWhileOrUntilEnd;
    }

    INSERT_BEFORE(&(DoGroup->SiblingListEntry), &(Node->Children));
    Result = TRUE;

ParseWhileOrUntilEnd:
    if (Result == FALSE) {
        if (Node != NULL) {
            ShReleaseNode(Node);
            Node = NULL;
        }
    }

    return Node;
}

PSHELL_NODE
ShParseFor (
    PSHELL Shell
    )

/*++

Routine Description:

    This routine attempts to parse a for statement.

Arguments:

    Shell - Supplies a pointer to the shell object.

Return Value:

    Returns a pointer to the node on success.

    NULL on failure.

--*/

{

    PSHELL_NODE DoGroup;
    PSHELL_NODE ForNode;
    BOOL LineBreakRequired;
    BOOL Result;

    assert(Shell->Lexer.TokenType == TOKEN_FOR);

    ForNode = NULL;
    Result = ShGetToken(Shell, FALSE);
    if (Result == FALSE) {
        goto ParseForEnd;
    }

    //
    // The next token is the name of the iterator variable.
    //

    if (Shell->Lexer.TokenType != TOKEN_WORD) {
        ShParseError(Shell, NULL, "Expected 'for' variable iterator name.");
        Result = FALSE;
        goto ParseForEnd;
    }

    Result = ShIsName(Shell->Lexer.TokenBuffer, Shell->Lexer.TokenBufferSize);
    if (Result == FALSE) {
        ShParseError(Shell, NULL, "Bad for loop variable name.");
        goto ParseForEnd;
    }

    ForNode = ShCreateNode(Shell, ShellNodeFor);
    if (ForNode == NULL) {
        Result = FALSE;
        goto ParseForEnd;
    }

    ForNode->U.For.Name = SwStringDuplicate(Shell->Lexer.TokenBuffer,
                                            Shell->Lexer.TokenBufferSize);

    if (ForNode->U.For.Name == NULL) {
        Result = FALSE;
        goto ParseForEnd;
    }

    ForNode->U.For.NameSize = Shell->Lexer.TokenBufferSize;
    Result = ShGetToken(Shell, FALSE);
    if (Result == FALSE) {
        goto ParseForEnd;
    }

    ShParseLineBreak(Shell, FALSE, FALSE);

    //
    // There can be an "in" next, but it's optional (the command line is used
    // if nothing's supplied).
    //

    LineBreakRequired = FALSE;
    if (Shell->Lexer.TokenType == TOKEN_IN) {
        LineBreakRequired = TRUE;
        Result = ShGetToken(Shell, FALSE);
        if (Result == FALSE) {
            goto ParseForEnd;
        }

        //
        // Now there's an optional wordlist, which means as long as words are
        // coming in add them to the wordlist.
        //

        while (SHELL_TOKEN_WORD_LIKE(Shell->Lexer.TokenType)) {
            Result = ShStringAppend(&(ForNode->U.For.WordListBuffer),
                                    &(ForNode->U.For.WordListBufferSize),
                                    &(ForNode->U.For.WordListBufferCapacity),
                                    Shell->Lexer.TokenBuffer,
                                    Shell->Lexer.TokenBufferSize);

            if (Result == FALSE) {
                goto ParseForEnd;
            }

            Result = ShGetToken(Shell, FALSE);
            if (Result == FALSE) {
                goto ParseForEnd;
            }
        }
    }

    //
    // Now parse a sequential separator, which is either a semicolon and
    // a linebreak or one or more newlines.
    //

    if (Shell->Lexer.TokenType == ';') {
        Result = ShGetToken(Shell, TRUE);
        if (Result == FALSE) {
            goto ParseForEnd;
        }

        Result = ShParseLineBreak(Shell, FALSE, FALSE);

    } else {
        Result = ShParseLineBreak(Shell, LineBreakRequired, FALSE);
    }

    if (Result == FALSE) {
        goto ParseForEnd;
    }

    //
    // Parse out a "do group" and call it a day.
    //

    DoGroup = ShParseDoGroup(Shell);
    if (DoGroup == NULL) {
        Result = FALSE;
        goto ParseForEnd;
    }

    INSERT_BEFORE(&(DoGroup->SiblingListEntry), &(ForNode->Children));
    Result = TRUE;

ParseForEnd:
    if (Result == FALSE) {
        if (ForNode != NULL) {
            ShReleaseNode(ForNode);
            ForNode = NULL;
        }
    }

    return ForNode;
}

PSHELL_NODE
ShParseCase (
    PSHELL Shell
    )

/*++

Routine Description:

    This routine attempts to parse a case statement.

Arguments:

    Shell - Supplies a pointer to the shell object.

Return Value:

    Returns a pointer to the node on success.

    NULL on failure.

--*/

{

    PSHELL_NODE CaseNode;
    PSHELL_CASE_PATTERN_SET PatternSet;
    BOOL Result;

    CaseNode = NULL;

    assert(Shell->Lexer.TokenType == TOKEN_CASE);

    Result = ShGetToken(Shell, FALSE);
    if (Result == FALSE) {
        goto ParseCaseEnd;
    }

    //
    // After the word case comes the name of the thing to match against.
    //

    if (!SHELL_TOKEN_WORD_LIKE(Shell->Lexer.TokenType)) {
        ShParseError(Shell, NULL, "Expected case input word.");
        Result = FALSE;
        goto ParseCaseEnd;
    }

    CaseNode = ShCreateNode(Shell, ShellNodeCase);
    if (CaseNode == NULL) {
        Result = FALSE;
        goto ParseCaseEnd;
    }

    CaseNode->U.Case.Name = SwStringDuplicate(Shell->Lexer.TokenBuffer,
                                              Shell->Lexer.TokenBufferSize);

    if (CaseNode->U.Case.Name == NULL) {
        Result = FALSE;
        goto ParseCaseEnd;
    }

    CaseNode->U.Case.NameSize = Shell->Lexer.TokenBufferSize;
    Result = ShGetToken(Shell, FALSE);
    if (Result == FALSE) {
        goto ParseCaseEnd;
    }

    //
    // Then parse a linebreak.
    //

    Result = ShParseLineBreak(Shell, FALSE, FALSE);
    if (Result == FALSE) {
        goto ParseCaseEnd;
    }

    //
    // Then parse an "in" and another linebreak.
    //

    if (Shell->Lexer.TokenType != TOKEN_IN) {
        ShParseError(Shell, CaseNode, "Expected 'in'.");
        Result = FALSE;
        goto ParseCaseEnd;
    }

    Result = ShGetToken(Shell, FALSE);
    if (Result == FALSE) {
        goto ParseCaseEnd;
    }

    Result = ShParseLineBreak(Shell, FALSE, FALSE);
    if (Result == FALSE) {
        goto ParseCaseEnd;
    }

    //
    // Here comes the case list, which is actually optional altogether.
    //

    while (TRUE) {

        //
        // Parse past an optional opening parentheses.
        //

        if (Shell->Lexer.TokenType == '(') {
            Result = ShGetToken(Shell, FALSE);
            if (Result == FALSE) {
                goto ParseCaseEnd;
            }
        }

        //
        // Scan the pattern(s).
        //

        Result = ShParsePattern(Shell, CaseNode, &PatternSet);
        if (Result == FALSE) {
            goto ParseCaseEnd;
        }

        //
        // If the pattern set is null, this is an esac, stop parsing patterns.
        //

        if (PatternSet == NULL) {

            assert(Shell->Lexer.TokenType == TOKEN_ESAC);

            break;
        }

        //
        // Scan the closing parentheses.
        //

        if (Shell->Lexer.TokenType != ')') {
            ShParseError(Shell, NULL, "Expected ')' to close case pattern.");
            Result = FALSE;
            goto ParseCaseEnd;
        }

        Result = ShGetToken(Shell, TRUE);
        if (Result == FALSE) {
            goto ParseCaseEnd;
        }

        //
        // Scan an optional linebreak.
        //

        Result = ShParseLineBreak(Shell, FALSE, TRUE);
        if (Result == FALSE) {
            goto ParseCaseEnd;
        }

        //
        // Now there's either a compound list there, a double semicolon, or an
        // esac.
        //

        if (Shell->Lexer.TokenType == TOKEN_DOUBLE_SEMICOLON) {
            Result = ShGetToken(Shell, FALSE);
            if (Result == FALSE) {
                goto ParseCaseEnd;
            }

            Result = ShParseLineBreak(Shell, FALSE, TRUE);
            if (Result == FALSE) {
                goto ParseCaseEnd;
            }

        } else if (Shell->Lexer.TokenType == TOKEN_ESAC) {
            break;

        //
        // It must be a compound list.
        //

        } else {
            PatternSet->Action = ShParseCompoundList(Shell);
            if (PatternSet->Action == NULL) {
                goto ParseCaseEnd;
            }

            //
            // Parse an optional linebreak.
            //

            Result = ShParseLineBreak(Shell, FALSE, FALSE);
            if (Result == FALSE) {
                goto ParseCaseEnd;
            }

            //
            // Now there had better be an esac or a double semicolon there.
            //

            if (Shell->Lexer.TokenType == TOKEN_ESAC) {
                break;

            } else if (Shell->Lexer.TokenType != TOKEN_DOUBLE_SEMICOLON) {
                ShParseError(Shell,
                             NULL,
                             "Expected ';;' for case at line %d.",
                             CaseNode->LineNumber);

                Result = FALSE;
                goto ParseCaseEnd;
            }

            //
            // Scan over the double semicolon and an optional linebreak.
            //

            Result = ShGetToken(Shell, FALSE);
            if (Result == FALSE) {
                goto ParseCaseEnd;
            }

            Result = ShParseLineBreak(Shell, FALSE, FALSE);
            if (Result == FALSE) {
                goto ParseCaseEnd;
            }
        }
    }

    //
    // Scan over the esac.
    //

    if (Shell->Lexer.TokenType != TOKEN_ESAC) {
        ShParseError(Shell,
                     NULL,
                     "Expected 'esac' for case at line %d.",
                     CaseNode->LineNumber);

        Result = FALSE;
        goto ParseCaseEnd;
    }

    Result = ShGetToken(Shell, TRUE);
    if (Result == FALSE) {
        goto ParseCaseEnd;
    }

ParseCaseEnd:
    if (Result == FALSE) {
        if (CaseNode != NULL) {
            ShReleaseNode(CaseNode);
            CaseNode = NULL;
        }
    }

    return CaseNode;
}

BOOL
ShParsePattern (
    PSHELL Shell,
    PSHELL_NODE Case,
    PSHELL_CASE_PATTERN_SET *NewPatternSet
    )

/*++

Routine Description:

    This routine attempts to scan a pattern, which is a sequence of 1 or more
    words separated by bars (|).

Arguments:

    Shell - Supplies a pointer to the shell object.

    Case - Supplies a pointer to the case node where the pattern will be added.

    NewPatternSet - Supplies a pointer where the new pattern set will be
        returned on success.

Return Value:

    Returns a pointer to the created case pattern on success.

    NULL on failure.

--*/

{

    BOOL GotSomething;
    BOOL Result;
    PSHELL_CASE_PATTERN_SET Set;

    assert(Case->Type == ShellNodeCase);

    //
    // Create a pattern set and optimistically add it to the case statement.
    //

    Set = malloc(sizeof(SHELL_CASE_PATTERN_SET));
    if (Set == NULL) {
        return FALSE;
    }

    memset(Set, 0, sizeof(SHELL_CASE_PATTERN_SET));
    INITIALIZE_LIST_HEAD(&(Set->PatternEntryList));
    INSERT_BEFORE(&(Set->ListEntry), &(Case->U.Case.PatternList));

    //
    // Loop through and try to get at least one pattern word out.
    //

    GotSomething = FALSE;
    while (SHELL_TOKEN_WORD_LIKE(Shell->Lexer.TokenType)) {

        //
        // If it's an esac on the first pattern, then treat it with respect.
        //

        if ((Shell->Lexer.TokenType == TOKEN_ESAC) && (GotSomething == FALSE)) {
            break;
        }

        //
        // Add the new pattern word to this set.
        //

        Result = ShAddPatternToSet(Set,
                                   Shell->Lexer.TokenBuffer,
                                   Shell->Lexer.TokenBufferSize);

        if (Result == FALSE) {
            goto ParsePatternEnd;
        }

        GotSomething = TRUE;
        Result = ShGetToken(Shell, FALSE);
        if (Result == FALSE) {
            goto ParsePatternEnd;
        }

        //
        // If the next thing isn't a pipe, then this pattern list is over.
        //

        if (Shell->Lexer.TokenType != '|') {
            break;
        }

        Result = ShGetToken(Shell, FALSE);
        if (Result == FALSE) {
            goto ParsePatternEnd;
        }
    }

    //
    // If it was an esac and nothing else, take the set back off the list, this
    // case statement is over.
    //

    if ((GotSomething == FALSE) && (Shell->Lexer.TokenType == TOKEN_ESAC)) {

        assert((Set->Action == NULL) && (LIST_EMPTY(&(Set->PatternEntryList))));

        LIST_REMOVE(&(Set->ListEntry));
        free(Set);
        Set = NULL;
        Result = TRUE;

    } else {
        Result = GotSomething;
        if (Result == FALSE) {
            ShParseError(Shell, Case, "Expected pattern word.");
        }
    }

ParsePatternEnd:
    *NewPatternSet = Set;
    return Result;
}

PSHELL_NODE
ShParseDoGroup (
    PSHELL Shell
    )

/*++

Routine Description:

    This routine attempts to parse a "do group", which consists of "do", a
    compound list, and "done". This is used in most loop constructs.

Arguments:

    Shell - Supplies a pointer to the shell object.

Return Value:

    Returns a pointer to the node on success.

    NULL on failure.

--*/

{

    PSHELL_NODE CompoundList;
    ULONG DoLineNumber;
    BOOL Result;

    CompoundList = NULL;
    ShParseLineBreak(Shell, FALSE, FALSE);

    //
    // The first thing in a do group should really be a do.
    //

    if (Shell->Lexer.TokenType != TOKEN_DO) {
        ShParseError(Shell, NULL, "Expected 'do'.");
        Result = FALSE;
        goto ParseDoGroupEnd;
    }

    DoLineNumber = Shell->Lexer.LineNumber;
    Result = ShGetToken(Shell, TRUE);
    if (Result == FALSE) {
        goto ParseDoGroupEnd;
    }

    //
    // Now get the compound list.
    //

    CompoundList = ShParseCompoundList(Shell);
    if (CompoundList == NULL) {
        goto ParseDoGroupEnd;
    }

    //
    // Now there should be a done.
    //

    if (Shell->Lexer.TokenType != TOKEN_DONE) {
        ShParseError(Shell,
                     NULL,
                     "Expected 'done' for 'do' at line %d.",
                     DoLineNumber);

        Result = FALSE;
        goto ParseDoGroupEnd;
    }

    Result = ShGetToken(Shell, TRUE);
    if (Result == FALSE) {
        goto ParseDoGroupEnd;
    }

ParseDoGroupEnd:
    if (Result == FALSE) {
        if (CompoundList != NULL) {
            ShReleaseNode(CompoundList);
            CompoundList = NULL;
        }
    }

    return CompoundList;
}

PSHELL_NODE
ShParseCompoundList (
    PSHELL Shell
    )

/*++

Routine Description:

    This routine attempts to parse a compound list.

Arguments:

    Shell - Supplies a pointer to the shell object.

Return Value:

    Returns a pointer to the node on success.

    NULL on failure.

--*/

{

    PSHELL_NODE CompoundList;
    BOOL Result;
    CHAR Separator;

    CompoundList = NULL;

    //
    // Parse a line break (really just an optional newline list).
    //

    Result = ShParseLineBreak(Shell, FALSE, TRUE);
    if (Result == FALSE) {
        goto ParseCompoundListEnd;
    }

    //
    // Parse a term, call it a compound list.
    //

    CompoundList = ShParseTerm(Shell);
    if (CompoundList == NULL) {
        Result = FALSE;
        goto ParseCompoundListEnd;
    }

    //
    // Parse an optional separator.
    //

    Result = ShParseSeparator(Shell, &Separator);
    if (Result != FALSE) {
        if (Separator == '&') {
            CompoundList->RunInBackground = TRUE;
        }
    }

    Result = TRUE;

ParseCompoundListEnd:
    if (Result == FALSE) {
        if (CompoundList != NULL) {
            ShReleaseNode(CompoundList);
            CompoundList = NULL;
        }
    }

    return CompoundList;
}

PSHELL_NODE
ShParseTerm (
    PSHELL Shell
    )

/*++

Routine Description:

    This routine attempts to parse a term out of the shell input.

Arguments:

    Shell - Supplies a pointer to the shell object.

Return Value:

    Returns a pointer to the node on success.

    NULL on failure.

--*/

{

    PSHELL_NODE AndOr;
    BOOL Result;
    CHAR SeparatorOp;
    PSHELL_NODE Term;

    Result = FALSE;
    Term = NULL;

    //
    // Parse and-or statements separated by separators (this is different than
    // a list where the separators were separator ops only).
    //

    while (TRUE) {
        AndOr = ShParseAndOr(Shell);
        if (AndOr == NULL) {
            if (Term == NULL) {
                Result = FALSE;
                ShParseError(Shell, NULL, "Unexpected token.");
                goto ParseTermEnd;
            }

            break;
        }

        Result = ShParseSeparator(Shell, &SeparatorOp);

        //
        // If this is the first time around and there's only one item, then
        // just return that item.
        //

        if ((Result == FALSE) && (Term == NULL)) {
            Term = AndOr;
            break;
        }

        //
        // There's more than one and-or. If the list has yet to be made, make it
        // now.
        //

        if (Term == NULL) {
            Term = ShCreateNode(Shell, ShellNodeTerm);
            if (Term == NULL) {
                goto ParseTermEnd;
            }
        }

        INSERT_BEFORE(&(AndOr->SiblingListEntry), &(Term->Children));

        //
        // Regardless of where the command was, if there's no separator, this
        // loop is done.
        //

        if (Result == FALSE) {
            break;
        }

        if (SeparatorOp == '&') {
            AndOr->RunInBackground = TRUE;
        }
    }

    AndOr = NULL;
    Result = TRUE;

ParseTermEnd:
    if (AndOr != NULL) {
        ShReleaseNode(AndOr);
    }

    if (Result == FALSE) {
        if (Term != NULL) {
            ShReleaseNode(Term);
            Term = NULL;
        }
    }

    return Term;
}

PSHELL_NODE
ShParseSimpleCommandOrFunction (
    PSHELL Shell
    )

/*++

Routine Description:

    This routine attempts to parse a simple command or function definition.

Arguments:

    Shell - Supplies a pointer to the shell object.

Return Value:

    Returns a pointer to the node on success.

    NULL on failure.

--*/

{

    PSHELL_NODE Command;
    PSTR FirstWord;
    UINTN FirstWordSize;
    BOOL Result;

    Command = NULL;

    //
    // Get the first word and look beyond it.
    //

    FirstWord = NULL;
    FirstWordSize = 0;
    if (Shell->Lexer.TokenType == TOKEN_WORD) {
        FirstWordSize = Shell->Lexer.TokenBufferSize;
        FirstWord = SwStringDuplicate(Shell->Lexer.TokenBuffer, FirstWordSize);
        if (FirstWord == NULL) {
            Result = FALSE;
            goto ParseSimpleCommandOrFunctionEnd;
        }

        Result = ShGetToken(Shell, FALSE);
        if (Result == FALSE) {
            goto ParseSimpleCommandOrFunctionEnd;
        }
    }

    //
    // If the next thing is an open parentheses, then it's a function
    // definition. Otherwise it's a simple command.
    //

    if ((ShIsName(FirstWord, FirstWordSize) != FALSE) &&
        (Shell->Lexer.TokenType == '(')) {

        Command = ShParseFunctionDefinition(Shell, FirstWord, FirstWordSize);

    } else {
        Command = ShParseSimpleCommand(Shell, FirstWord, FirstWordSize);
    }

ParseSimpleCommandOrFunctionEnd:
    if (FirstWord != NULL) {
        free(FirstWord);
    }

    return Command;
}

PSHELL_NODE
ShParseSimpleCommand (
    PSHELL Shell,
    PSTR FirstWord,
    UINTN FirstWordSize
    )

/*++

Routine Description:

    This routine attempts to parse a simple command.

Arguments:

    Shell - Supplies a pointer to the shell object.

    FirstWord - Supplies an optional pointer to the command name that was
        parsed out earlier.

    FirstWordSize - Supplies the size of the first word buffer in bytes
        including the null terminator.

Return Value:

    Returns a pointer to the node on success.

    NULL on failure.

--*/

{

    BOOL AllowAssignmentWords;
    PSHELL_NODE Command;
    BOOL NonEmpty;
    BOOL Result;
    BOOL SwallowToken;

    NonEmpty = FALSE;
    Result = TRUE;
    Command = ShCreateNode(Shell, ShellNodeSimpleCommand);
    if (Command == NULL) {
        return NULL;
    }

    AllowAssignmentWords = TRUE;
    if (FirstWord != NULL) {
        AllowAssignmentWords = FALSE;
        Result = ShAddComponentToCommand(Command, FirstWord, FirstWordSize);
        if (Result == FALSE) {
            goto ParseSimpleCommandEnd;
        }

        NonEmpty = TRUE;
    }

    while (TRUE) {
        SwallowToken = TRUE;
        switch (Shell->Lexer.TokenType) {
        case TOKEN_IO_NUMBER:
        case TOKEN_LESS_THAN_AND:
        case TOKEN_GREATER_THAN_AND:
        case TOKEN_DOUBLE_GREATER_THAN:
        case TOKEN_DOUBLE_LESS_THAN:
        case TOKEN_DOUBLE_LESS_THAN_DASH:
        case TOKEN_LESS_THAN_GREATER_THAN:
        case TOKEN_CLOBBER:
        case '>':
        case '<':
            Result = ShParseRedirection(Shell, Command);
            if (Result == FALSE) {
                goto ParseSimpleCommandEnd;
            }

            SwallowToken = FALSE;
            break;

        case TOKEN_ASSIGNMENT_WORD:

            //
            // If still at that phase (before the initial command word) parse
            // any assignment words that come out. If the assignment word was
            // miscategorized, fall through to the regular word processing.
            //

            if (AllowAssignmentWords != FALSE) {
                Result = ShParseAssignment(Shell, Command);
                if (Result != FALSE) {
                    break;
                }

                Result = TRUE;
            }

            //
            // If assignment words are not allowed or the assignment word
            // failed, fall through.
            //

        //
        // Inside of a simple command, they keywords are just regular arguments.
        //

        case TOKEN_IF:
        case TOKEN_THEN:
        case TOKEN_ELSE:
        case TOKEN_ELIF:
        case TOKEN_FI:
        case TOKEN_DO:
        case TOKEN_DONE:
        case TOKEN_CASE:
        case TOKEN_ESAC:
        case TOKEN_WHILE:
        case TOKEN_UNTIL:
        case TOKEN_FOR:
        case TOKEN_IN:
        case TOKEN_WORD:
        case '!':
        case '{':
        case '}':
            Result = ShAddComponentToCommand(Command,
                                             Shell->Lexer.TokenBuffer,
                                             Shell->Lexer.TokenBufferSize);

            if (Result == FALSE) {
                goto ParseSimpleCommandEnd;
            }

            AllowAssignmentWords = FALSE;
            break;

        default:
            Result = NonEmpty;
            if (Result == FALSE) {
                ShParseError(Shell, Command, "Expected simple command word.");
            }

            goto ParseSimpleCommandEnd;
        }

        NonEmpty = TRUE;
        if (SwallowToken != FALSE) {
            Result = ShGetToken(Shell, FALSE);
            if (Result == FALSE) {
                goto ParseSimpleCommandEnd;
            }
        }
    }

ParseSimpleCommandEnd:
    if (Result == FALSE) {
        if (Command != NULL) {
            ShReleaseNode(Command);
            Command = NULL;
        }
    }

    return Command;
}

PSHELL_NODE
ShParseFunctionDefinition (
    PSHELL Shell,
    PSTR FunctionName,
    UINTN FunctionNameSize
    )

/*++

Routine Description:

    This routine attempts to parse a function definition.

Arguments:

    Shell - Supplies a pointer to the shell object.

    FunctionName - Supplies a pointer to the function name.

    FunctionNameSize - Supplies the size of the function name buffer in bytes
        including the null terminator.

Return Value:

    Returns a pointer to the node on success.

    NULL on failure.

--*/

{

    PSHELL_NODE Body;
    PSHELL_NODE Function;
    BOOL Result;

    Result = TRUE;
    Function = ShCreateNode(Shell, ShellNodeFunction);
    if (Function == NULL) {
        return NULL;
    }

    Function->U.Function.Name = SwStringDuplicate(FunctionName,
                                                  FunctionNameSize);

    if (Function->U.Function.Name == NULL) {
        Result = FALSE;
        goto ParseFunctionDefinitionEnd;
    }

    Function->U.Function.NameSize = FunctionNameSize;

    //
    // The current token should be an open parentheses, and then there should
    // be a close parenthese and a newline.
    //

    assert(Shell->Lexer.TokenType == '(');

    Result = ShGetToken(Shell, FALSE);
    if (Result == FALSE) {
        goto ParseFunctionDefinitionEnd;
    }

    if (Shell->Lexer.TokenType != ')') {
        ShParseError(Shell,
                     Function,
                     "Expected ')' for function definition.");

        Result = FALSE;
        goto ParseFunctionDefinitionEnd;
    }

    Result = ShGetToken(Shell, TRUE);
    if (Result == FALSE) {
        goto ParseFunctionDefinitionEnd;
    }

    Result = ShParseLineBreak(Shell, FALSE, TRUE);
    if (Result == FALSE) {
        goto ParseFunctionDefinitionEnd;
    }

    Body = ShParseCompoundCommand(Shell);
    if (Body == NULL) {
        goto ParseFunctionDefinitionEnd;
    }

    INSERT_BEFORE(&(Body->SiblingListEntry), &(Function->Children));

    //
    // Parse an optional redirect list on the function body.
    //

    Result = ShParseOptionalRedirectList(Shell, Function);
    if (Result == FALSE) {
        goto ParseFunctionDefinitionEnd;
    }

    Result = TRUE;

ParseFunctionDefinitionEnd:
    if (Result == FALSE) {
        if (Function != NULL) {
            ShReleaseNode(Function);
            Function = NULL;
        }
    }

    return Function;
}

BOOL
ShParseOptionalRedirectList (
    PSHELL Shell,
    PSHELL_NODE Node
    )

/*++

Routine Description:

    This routine attempts to parse a redirect list that may or may not be there.

Arguments:

    Shell - Supplies a pointer to the shell object.

    Node - Supplies a pointer to the node the I/O redirection belongs to.

Return Value:

    TRUE on success.

    FALSE on failure.

--*/

{

    BOOL BreakOut;
    BOOL Result;

    Result = TRUE;
    BreakOut = FALSE;
    while (BreakOut == FALSE) {
        switch (Shell->Lexer.TokenType) {
        case TOKEN_IO_NUMBER:
        case TOKEN_LESS_THAN_AND:
        case TOKEN_GREATER_THAN_AND:
        case TOKEN_DOUBLE_GREATER_THAN:
        case TOKEN_DOUBLE_LESS_THAN:
        case TOKEN_DOUBLE_LESS_THAN_DASH:
        case TOKEN_LESS_THAN_GREATER_THAN:
        case TOKEN_CLOBBER:
        case '>':
        case '<':
            Result = ShParseRedirection(Shell, Node);
            if (Result == FALSE) {
                goto ParseOptionalRedirectListEnd;
            }

            break;

        default:
            BreakOut = TRUE;
            break;
        }
    }

ParseOptionalRedirectListEnd:
    return Result;
}

BOOL
ShParseRedirection (
    PSHELL Shell,
    PSHELL_NODE Node
    )

/*++

Routine Description:

    This routine attempts to parse an I/O redirection.

Arguments:

    Shell - Supplies a pointer to the shell object.

    Node - Supplies a pointer to the node the I/O redirection belongs to.

Return Value:

    TRUE on success.

    FALSE on failure.

--*/

{

    ULONG DefaultFileNumber;
    PSTR FileName;
    UINTN FileNameSize;
    ULONG FileNumber;
    BOOL Result;
    SHELL_IO_REDIRECTION_TYPE Type;

    DefaultFileNumber = -1;
    FileNumber = -1;
    Type = ShellRedirectInvalid;

    //
    // Start by attempting to get an I/O number.
    //

    if (Shell->Lexer.TokenType == TOKEN_IO_NUMBER) {
        FileNumber = strtol(Shell->Lexer.TokenBuffer, NULL, 10);
        if (FileNumber < 0) {

            //
            // The lexer should have stripped off any piece that couldn't be
            // interpreted as a positive number, so it's weird that this
            // number wouldn't convert.
            //

            assert(FALSE);

            Result = FALSE;
            goto ParseRedirectionEnd;
        }

        Result = ShGetToken(Shell, FALSE);
        if (Result == FALSE) {
            goto ParseRedirectionEnd;
        }
    }

    //
    // Now get the operator.
    //

    switch (Shell->Lexer.TokenType) {
    case TOKEN_LESS_THAN_AND:
        Type = ShellRedirectReadFromDescriptor;
        DefaultFileNumber = STDIN_FILENO;
        break;

    case TOKEN_GREATER_THAN_AND:
        Type = ShellRedirectWriteToDescriptor;
        DefaultFileNumber = STDOUT_FILENO;
        break;

    case TOKEN_DOUBLE_GREATER_THAN:
        Type = ShellRedirectAppend;
        DefaultFileNumber = STDOUT_FILENO;
        break;

    case TOKEN_DOUBLE_LESS_THAN:
        Type = ShellRedirectHereDocument;
        DefaultFileNumber = STDIN_FILENO;
        break;

    case TOKEN_DOUBLE_LESS_THAN_DASH:
        Type = ShellRedirectStrippedHereDocument;
        DefaultFileNumber = STDIN_FILENO;
        break;

    case TOKEN_LESS_THAN_GREATER_THAN:
        Type = ShellRedirectReadWrite;
        DefaultFileNumber = STDIN_FILENO;
        break;

    case TOKEN_CLOBBER:
        Type = ShellRedirectClobber;
        DefaultFileNumber = STDOUT_FILENO;
        break;

    case '>':
        Type = ShellRedirectWrite;
        DefaultFileNumber = STDOUT_FILENO;
        break;

    case '<':
        Type = ShellRedirectRead;
        DefaultFileNumber = STDIN_FILENO;
        break;

    default:
        Result = FALSE;
        goto ParseRedirectionEnd;
    }

    Result = ShGetToken(Shell, FALSE);
    if (Result == FALSE) {
        goto ParseRedirectionEnd;
    }

    //
    // Now get the word of where to redirect to. Convert to a file descriptor
    // number if needed.
    //

    if (!SHELL_TOKEN_WORD_LIKE(Shell->Lexer.TokenType)) {
        ShParseError(Shell, Node, "Expected redirection file name.");
        Result = FALSE;
        goto ParseRedirectionEnd;
    }

    FileName = Shell->Lexer.TokenBuffer;
    FileNameSize = Shell->Lexer.TokenBufferSize;
    if (FileNumber == -1) {
        FileNumber = DefaultFileNumber;
    }

    Result = ShCreateRedirection(Shell,
                                 Node,
                                 Type,
                                 FileNumber,
                                 FileName,
                                 FileNameSize);

    if (Result == FALSE) {
        goto ParseRedirectionEnd;
    }

    Result = ShGetToken(Shell, TRUE);
    if (Result == FALSE) {
        goto ParseRedirectionEnd;
    }

ParseRedirectionEnd:
    return Result;
}

BOOL
ShParseAssignment (
    PSHELL Shell,
    PSHELL_NODE Node
    )

/*++

Routine Description:

    This routine attempts to parse an assignment word.

Arguments:

    Shell - Supplies a pointer to the shell object.

    Node - Supplies a pointer to the node the assignment belongs to.

Return Value:

    TRUE on success.

    FALSE on failure.

--*/

{

    PSTR Name;
    UINTN NameSize;
    BOOL Result;
    PSTR Token;
    UINTN TokenSize;
    PSTR Value;
    UINTN ValueSize;

    Token = Shell->Lexer.TokenBuffer;
    TokenSize = Shell->Lexer.TokenBufferSize;
    Name = Token;
    Value = strchr(Token, '=');
    if (Value == NULL) {

        //
        // How would something get here unless this was already categorized as
        // an assignment word?
        //

        assert(FALSE);

        return FALSE;
    }

    //
    // There can't be a zero length name.
    //

    if (Value == Token) {
        return FALSE;
    }

    NameSize = ((UINTN)Value - (UINTN)Token);
    if (ShIsName(Name, NameSize) == FALSE) {
        return FALSE;
    }

    NameSize += 1;
    Value += 1;
    ValueSize = TokenSize - ((UINTN)Value - (UINTN)Token);
    Result = ShCreateAssignment(Node, Name, NameSize, Value, ValueSize);
    return Result;
}

BOOL
ShParseLineBreak (
    PSHELL Shell,
    BOOL Required,
    BOOL FirstCommandWord
    )

/*++

Routine Description:

    This routine parses a line break term, which is just zero or more newline.

Arguments:

    Shell - Supplies a pointer to the shell whose input is being parsed.

    Required - Supplies a boolean indicating if at least one line break is
        required.

    FirstCommandWord - Supplies the boolean that will be passed to the get
        token routine indicating whether or not this next token could be the
        command word of a simple command.

Return Value:

    TRUE on success.

    FALSE if the lexer failed to get a token.

--*/

{

    BOOL Result;

    if ((Required != FALSE) && (Shell->Lexer.TokenType != '\n')) {
        return FALSE;
    }

    while (Shell->Lexer.TokenType == '\n') {
        ShPrintPrompt(Shell, 2);
        Result = ShGetToken(Shell, FirstCommandWord);
        if (Result == FALSE) {
            return FALSE;
        }
    }

    return TRUE;
}

BOOL
ShParseSeparator (
    PSHELL Shell,
    PCHAR Separator
    )

/*++

Routine Description:

    This routine attempts to parse a separator, which is either a separator op
    followed by a linebreak, or 1+ newlines.

Arguments:

    Shell - Supplies a pointer to the shell whose input is being parsed.

    Separator - Supplies a pointer where the separator character will be
        returned on success, or 0 if there was no separator.

Return Value:

    TRUE if a separator operator was parsed.

    FALSE if there was no separator or the lexer failed to move on.

--*/

{

    BOOL Result;

    //
    // First try to get a separator op and linebreak. A linebreak swallows a
    // newline list.
    //

    Result = ShParseSeparatorOp(Shell, Separator);
    if (Result != FALSE) {
        Result = ShParseLineBreak(Shell, FALSE, TRUE);

    //
    // There's no separator op, try to get at least one newline.
    //

    } else {
        Result = ShParseLineBreak(Shell, TRUE, TRUE);
    }

    return Result;
}

BOOL
ShParseSeparatorOp (
    PSHELL Shell,
    PCHAR Separator
    )

/*++

Routine Description:

    This routine attempts to parse a separator operator, which is either a
    semicolon or an ampersand.

Arguments:

    Shell - Supplies a pointer to the shell whose input is being parsed.

    Separator - Supplies a pointer where the separator character will be
        returned on success, or 0 if there was no separator.

Return Value:

    TRUE if a separator operator was parsed.

    FALSE if there was no separator or the lexer failed to move on.

--*/

{

    BOOL Result;

    if ((Shell->Lexer.TokenType == ';') || (Shell->Lexer.TokenType == '&')) {
        *Separator = Shell->Lexer.TokenType;
        Result = ShGetToken(Shell, TRUE);
        return Result;
    }

    return FALSE;
}

PSHELL_NODE
ShCreateNode (
    PSHELL Shell,
    SHELL_NODE_TYPE Type
    )

/*++

Routine Description:

    This routine allocates and initializes a shell node.

Arguments:

    Shell - Supplies a pointer to the shell.

    Type - Supplies the type of node to create.

Return Value:

    Returns a pointer to the node on success.

    NULL on failure.

--*/

{

    PSHELL_NODE NewNode;

    NewNode = malloc(sizeof(SHELL_NODE));
    if (NewNode == NULL) {
        return NULL;
    }

    NewNode->Type = Type;
    NewNode->ReferenceCount = 1;
    NewNode->LineNumber = Shell->Lexer.LineNumber;
    if (Shell->Lexer.TokenType == '\n') {
        NewNode->LineNumber -= 1;
    }

    INITIALIZE_LIST_HEAD(&(NewNode->Children));
    INITIALIZE_LIST_HEAD(&(NewNode->RedirectList));
    NewNode->RunInBackground = FALSE;
    NewNode->AndOr = 0;
    switch (Type) {
    case ShellNodePipeline:
        NewNode->U.Pipeline.Bang = FALSE;
        break;

    case ShellNodeSimpleCommand:
        memset(&(NewNode->U.SimpleCommand),
               0,
               sizeof(SHELL_NODE_SIMPLE_COMMAND));

        INITIALIZE_LIST_HEAD(&(NewNode->U.SimpleCommand.AssignmentList));
        break;

    case ShellNodeFunction:
        memset(&(NewNode->U.Function), 0, sizeof(SHELL_NODE_FUNCTION));
        break;

    case ShellNodeFor:
        memset(&(NewNode->U.For), 0, sizeof(SHELL_NODE_FOR));
        break;

    case ShellNodeCase:
        memset(&(NewNode->U.Case), 0, sizeof(SHELL_NODE_CASE));
        INITIALIZE_LIST_HEAD(&(NewNode->U.Case.PatternList));
        break;

    default:
        break;
    }

    return NewNode;
}

VOID
ShDeleteNode (
    PSHELL_NODE Node
    )

/*++

Routine Description:

    This routine destroys a shell node.

Arguments:

    Node - Supplies a pointer to the node to destroy.

Return Value:

    None.

--*/

{

    PSHELL_ASSIGNMENT Assignment;
    PSHELL_NODE Child;
    PSHELL_IO_REDIRECT Redirect;

    assert(Node->ReferenceCount == 0);

    switch (Node->Type) {
    case ShellNodeSimpleCommand:
        while (LIST_EMPTY(&(Node->U.SimpleCommand.AssignmentList)) == FALSE) {
            Assignment = LIST_VALUE(Node->U.SimpleCommand.AssignmentList.Next,
                                    SHELL_ASSIGNMENT,
                                    ListEntry);

            ShDestroyAssignment(Assignment);
        }

        if (Node->U.SimpleCommand.Arguments != NULL) {
            free(Node->U.SimpleCommand.Arguments);
        }

        break;

    case ShellNodeFunction:
        if (Node->U.Function.Name != NULL) {
            free(Node->U.Function.Name);
        }

        break;

    case ShellNodeFor:
        if (Node->U.For.Name != NULL) {
            free(Node->U.For.Name);
        }

        if (Node->U.For.WordListBuffer != NULL) {
            free(Node->U.For.WordListBuffer);
        }

        break;

    case ShellNodeCase:
        ShDestroyCasePatternList(Node);
        if (Node->U.Case.Name != NULL) {
            free(Node->U.Case.Name);
        }

        break;

    default:
        break;
    }

    while (LIST_EMPTY(&(Node->RedirectList)) == FALSE) {
        Redirect = LIST_VALUE(Node->RedirectList.Next,
                              SHELL_IO_REDIRECT,
                              ListEntry);

        ShDestroyRedirection(Redirect);
    }

    while (LIST_EMPTY(&(Node->Children)) == FALSE) {
        Child = LIST_VALUE(Node->Children.Next, SHELL_NODE, SiblingListEntry);
        LIST_REMOVE(&(Child->SiblingListEntry));
        ShReleaseNode(Child);
    }

    free(Node);
    return;
}

VOID
ShPrintNode (
    PSHELL Shell,
    PSHELL_NODE Node,
    ULONG Depth
    )

/*++

Routine Description:

    This routine prints out a parsed shell node.

Arguments:

    Shell - Supplies a pointer to the shell.

    Node - Supplies a pointer to the node to print.

    Depth - Supplies the indentation depth to print it at.

Return Value:

    None.

--*/

{

    PSHELL_ASSIGNMENT Assignment;
    PSHELL_NODE Child;
    PLIST_ENTRY CurrentEntry;
    PLIST_ENTRY CurrentEntryEntry;
    ULONG DepthIndex;
    PSHELL_CASE_PATTERN_ENTRY Entry;
    PSHELL_IO_REDIRECT Redirect;
    PSHELL_CASE_PATTERN_SET Set;

    for (DepthIndex = 0; DepthIndex < Depth; DepthIndex += 1) {
        ShPrintTrace(Shell, " ");
    }

    ShPrintTrace(Shell, "Line %d ", Node->LineNumber);
    switch (Node->Type) {
    case ShellNodeInvalid:
        ShPrintTrace(Shell, "Invalid Node");
        break;

    case ShellNodeList:
        ShPrintTrace(Shell, "List");
        break;

    case ShellNodeAndOr:
        ShPrintTrace(Shell, "AndOr");
        break;

    case ShellNodePipeline:
        if (Node->U.Pipeline.Bang != FALSE) {
            ShPrintTrace(Shell, "! ");
        }

        ShPrintTrace(Shell, "Pipeline");
        break;

    case ShellNodeSimpleCommand:
        ShPrintTrace(Shell, "SimpleCommand:");
        CurrentEntry = Node->U.SimpleCommand.AssignmentList.Next;
        while (CurrentEntry != &(Node->U.SimpleCommand.AssignmentList)) {
            Assignment = LIST_VALUE(CurrentEntry, SHELL_ASSIGNMENT, ListEntry);
            CurrentEntry = CurrentEntry->Next;
            ShPrintTrace(Shell,
                         " [%s]=[%s]",
                         Assignment->Name,
                         Assignment->Value);
        }

        ShPrintTrace(Shell, " [%s] ", Node->U.SimpleCommand.Arguments);
        break;

    case ShellNodeFunction:
        ShPrintTrace(Shell, "Function %s", Node->U.Function.Name);
        break;

    case ShellNodeIf:
        ShPrintTrace(Shell, "If");
        break;

    case ShellNodeTerm:
        ShPrintTrace(Shell, "Term");
        break;

    case ShellNodeFor:
        ShPrintTrace(Shell,
                     "For [%s] in [%s]",
                     Node->U.For.Name,
                     Node->U.For.WordListBuffer);

        ShPrintTrace(Shell, " do");
        break;

    case ShellNodeBraceGroup:
        ShPrintTrace(Shell, "BraceGroup");
        break;

    case ShellNodeCase:
        ShPrintTrace(Shell, "Case [%s]", Node->U.Case.Name);
        break;

    case ShellNodeWhile:
        ShPrintTrace(Shell, "While");
        break;

    case ShellNodeUntil:
        ShPrintTrace(Shell, "Until");
        break;

    case ShellNodeSubshell:
        ShPrintTrace(Shell, "Subshell");
        break;

    default:

        assert(FALSE);

        break;
    }

    CurrentEntry = Node->RedirectList.Next;
    while (CurrentEntry != &(Node->RedirectList)) {
        Redirect = LIST_VALUE(CurrentEntry, SHELL_IO_REDIRECT, ListEntry);
        CurrentEntry = CurrentEntry->Next;
        switch (Redirect->Type) {
        case ShellRedirectInvalid:
            ShPrintTrace(Shell, " INVALID_REDIRECT");
            break;

        case ShellRedirectRead:
            ShPrintTrace(Shell,
                         " [%d<%s]",
                         Redirect->FileNumber,
                         Redirect->FileName);

            break;

        case ShellRedirectReadFromDescriptor:
            ShPrintTrace(Shell,
                         " [%d<&%s]",
                         Redirect->FileNumber,
                         Redirect->FileName);

            break;

        case ShellRedirectWrite:
            ShPrintTrace(Shell,
                         " [%d>%s]",
                         Redirect->FileNumber,
                         Redirect->FileName);

            break;

        case ShellRedirectWriteToDescriptor:
            ShPrintTrace(Shell,
                         " [%d>&%s]",
                         Redirect->FileNumber,
                         Redirect->FileName);

            break;

        case ShellRedirectClobber:
            ShPrintTrace(Shell,
                         " [%d>|%s]",
                         Redirect->FileNumber,
                         Redirect->FileName);

            break;

        case ShellRedirectAppend:
            ShPrintTrace(Shell,
                         " [%d>>%s]",
                         Redirect->FileNumber,
                         Redirect->FileName);

            break;

        case ShellRedirectReadWrite:
            ShPrintTrace(Shell,
                         " [%d<>%s]",
                         Redirect->FileNumber,
                         Redirect->FileName);

            break;

        case ShellRedirectHereDocument:
            ShPrintTrace(Shell,
                         " [%d<<]>>>>\n",
                         Redirect->FileNumber);

            ShPrintTrace(Shell,
                         "%s\n<<<<",
                         Redirect->HereDocument->Document);

            break;

        case ShellRedirectStrippedHereDocument:
            ShPrintTrace(Shell, " [%d<<-]>>>>\n", Redirect->FileNumber);
            ShPrintTrace(Shell, "%s\n<<<<", Redirect->HereDocument->Document);
            break;

        default:

            assert(FALSE);

            break;
        }
    }

    if (Node->RunInBackground != FALSE) {
        ShPrintTrace(Shell, " &");
    }

    if (Node->AndOr == TOKEN_DOUBLE_AND) {
        ShPrintTrace(Shell, " &&");

    } else if (Node->AndOr == TOKEN_DOUBLE_OR) {
        ShPrintTrace(Shell, " ||");
    }

    ShPrintTrace(Shell, "\n");
    if (Node->Type == ShellNodeCase) {

        //
        // Loop through every set in the case.
        //

        CurrentEntry = Node->U.Case.PatternList.Next;
        while (CurrentEntry != &(Node->U.Case.PatternList)) {
            Set = LIST_VALUE(CurrentEntry, SHELL_CASE_PATTERN_SET, ListEntry);
            CurrentEntry = CurrentEntry->Next;

            //
            // Loop through every entry in the set.
            //

            CurrentEntryEntry = Set->PatternEntryList.Next;
            while (CurrentEntryEntry != &(Set->PatternEntryList)) {
                Entry = LIST_VALUE(CurrentEntryEntry,
                                   SHELL_CASE_PATTERN_ENTRY,
                                   ListEntry);

                CurrentEntryEntry = CurrentEntryEntry->Next;
                for (DepthIndex = 0; DepthIndex < Depth + 1; DepthIndex += 1) {
                    ShPrintTrace(Shell, " ");
                }

                ShPrintTrace(Shell, "Pattern: %s\n", Entry->Pattern);
            }

            if (Set->Action != NULL) {
                ShPrintNode(Shell, Set->Action, Depth + 2);

            } else {
                for (DepthIndex = 0; DepthIndex < Depth + 2; DepthIndex += 1) {
                    ShPrintTrace(Shell, " ");
                }

                ShPrintTrace(Shell, "No Action");
            }
        }
    }

    CurrentEntry = Node->Children.Next;
    while (CurrentEntry != &(Node->Children)) {
        Child = LIST_VALUE(CurrentEntry, SHELL_NODE, SiblingListEntry);
        CurrentEntry = CurrentEntry->Next;
        ShPrintNode(Shell, Child, Depth + 1);
    }

    return;
}

BOOL
ShCreateRedirection (
    PSHELL Shell,
    PSHELL_NODE Node,
    SHELL_IO_REDIRECTION_TYPE Type,
    INT FileNumber,
    PSTR FileName,
    UINTN FileNameSize
    )

/*++

Routine Description:

    This routine creates a new I/O redirection entry and puts it on the list
    for the given node.

Arguments:

    Shell - Supplies a pointer to the shell the redirection is executing on.

    Node - Supplies a pointer to the shell node (probably a command).

    Type - Supplies the type of redirection.

    FileNumber - Supplies the file number being affected by the redirection.

    FileName - Supplies the optional name of the file being redirected to or
        from. If this parameter is not NULL, a copy of the given string will
        be made.

    FileNameSize - Supplies the size of the file name buffer in bytes.

Return Value:

    TRUE on success.

    FALSE on failure.

--*/

{

    PSHELL_HERE_DOCUMENT HereDocument;
    PSHELL_IO_REDIRECT Redirect;
    BOOL Result;

    HereDocument = NULL;
    Redirect = malloc(sizeof(SHELL_IO_REDIRECT));
    if (Redirect == NULL) {
        return FALSE;
    }

    memset(Redirect, 0, sizeof(SHELL_IO_REDIRECT));
    Redirect->Type = Type;
    Redirect->FileNumber = FileNumber;

    //
    // If it's a here document, create a new structure and add it to the
    // lexer's list of pending here documents.
    //

    if ((Redirect->Type == ShellRedirectHereDocument) ||
        (Redirect->Type == ShellRedirectStrippedHereDocument)) {

        HereDocument = malloc(sizeof(SHELL_HERE_DOCUMENT));
        if (HereDocument == NULL) {
            Result = FALSE;
            goto CreateRedirectionEnd;
        }

        memset(HereDocument, 0, sizeof(SHELL_HERE_DOCUMENT));
        if (Redirect->Type == ShellRedirectStrippedHereDocument) {
            HereDocument->StripLeadingTabs = TRUE;
        }

        if (ShIsStringQuoted(FileName) != FALSE) {
            HereDocument->EndWordWasQuoted = TRUE;
        }

        HereDocument->EndWord = SwStringDuplicate(FileName, FileNameSize);
        if (HereDocument->EndWord == NULL) {
            Result = FALSE;
            goto CreateRedirectionEnd;
        }

        HereDocument->EndWordSize = FileNameSize;
        ShStringDequote(HereDocument->EndWord,
                        HereDocument->EndWordSize,
                        0,
                        &(HereDocument->EndWordSize));

        INSERT_BEFORE(&(HereDocument->ListEntry),
                      &(Shell->Lexer.HereDocumentList));

        Redirect->HereDocument = HereDocument;

    } else if (FileName != NULL) {
        Redirect->FileNameSize = FileNameSize;
        Redirect->FileName = SwStringDuplicate(FileName, FileNameSize);
        if (Redirect->FileName == NULL) {
            Result = FALSE;
            goto CreateRedirectionEnd;
        }
    }

    INSERT_BEFORE(&(Redirect->ListEntry), &(Node->RedirectList));
    Result = TRUE;

CreateRedirectionEnd:
    if (Result == FALSE) {
        if (HereDocument != NULL) {
            ShDestroyHereDocument(HereDocument);
        }

        if (Redirect != NULL) {
            ShDestroyRedirection(Redirect);
        }
    }

    return Result;
}

VOID
ShDestroyRedirection (
    PSHELL_IO_REDIRECT Redirect
    )

/*++

Routine Description:

    This routine destroys an I/O redirection entry.

Arguments:

    Redirect - Supplies a pointer to the redirect entry.

Return Value:

    None.

--*/

{

    LIST_REMOVE(&(Redirect->ListEntry));
    if (Redirect->FileName != NULL) {
        free(Redirect->FileName);
    }

    if (Redirect->HereDocument != NULL) {
        if (Redirect->HereDocument->ListEntry.Next != NULL) {
            LIST_REMOVE(&(Redirect->HereDocument->ListEntry));
        }

        ShDestroyHereDocument(Redirect->HereDocument);
    }

    free(Redirect);
    return;
}

BOOL
ShCreateAssignment (
    PSHELL_NODE Node,
    PSTR Name,
    UINTN NameSize,
    PSTR Value,
    UINTN ValueSize
    )

/*++

Routine Description:

    This routine creates an assignment structure.

Arguments:

    Node - Supplies a pointer to the node to put the assignment on.

    Name - Supplies a pointer to the name string. A copy of this string will be
        made.

    NameSize - Supplies the length of the name string including a null
        terminator.

    Value - Supplies a pointer to the value string. A copy of this string will
        be made.

    ValueSize - Supplies the length of the value string including a null
        terminator.

Return Value:

    TRUE on success.

    FALSE on allocation failure.

--*/

{

    PSHELL_ASSIGNMENT Assignment;
    BOOL Result;

    Result = FALSE;
    Assignment = malloc(sizeof(SHELL_ASSIGNMENT));
    if (Assignment == NULL) {
        return FALSE;
    }

    memset(Assignment, 0, sizeof(SHELL_ASSIGNMENT));
    Assignment->Name = SwStringDuplicate(Name, NameSize);
    if (Assignment->Name == NULL) {
        goto CreateAssignmentEnd;
    }

    Assignment->NameSize = NameSize;
    Assignment->Value = SwStringDuplicate(Value, ValueSize);
    if (Assignment->Value == NULL) {
        goto CreateAssignmentEnd;
    }

    Assignment->ValueSize = ValueSize;

    assert(Node->Type == ShellNodeSimpleCommand);

    INSERT_BEFORE(&(Assignment->ListEntry),
                  &(Node->U.SimpleCommand.AssignmentList));

    Result = TRUE;

CreateAssignmentEnd:
    if (Result == FALSE) {
        if (Assignment != NULL) {
            if (Assignment->Name != NULL) {
                free(Assignment->Name);
            }

            if (Assignment->Value != NULL) {
                free(Assignment->Value);
            }

            free(Assignment);
        }
    }

    return Result;
}

VOID
ShDestroyAssignment (
    PSHELL_ASSIGNMENT Assignment
    )

/*++

Routine Description:

    This routine destroys an assignment entry.

Arguments:

    Assignment - Supplies a pointer to the assignment.

Return Value:

    None.

--*/

{

    LIST_REMOVE(&(Assignment->ListEntry));
    if (Assignment->Name != NULL) {
        free(Assignment->Name);
    }

    if (Assignment->Value != NULL) {
        free(Assignment->Value);
    }

    free(Assignment);
    return;
}

BOOL
ShAddPatternToSet (
    PSHELL_CASE_PATTERN_SET Set,
    PSTR Pattern,
    UINTN PatternSize
    )

/*++

Routine Description:

    This routine adds a case pattern entry to the given case statement.

Arguments:

    Set - Supplies a pointer to the set of patterns.

    Pattern - Supplies a pointer to the pattern string to add. A copy of this
        string will be made.

    PatternSize - Supplies the length of the pattern string in bytes including
        the null terminator.

Return Value:

    TRUE on success.

    FALSE on failure.

--*/

{

    PSHELL_CASE_PATTERN_ENTRY Entry;

    Entry = malloc(sizeof(SHELL_CASE_PATTERN_ENTRY));
    if (Entry == NULL) {
        return FALSE;
    }

    Entry->Pattern = SwStringDuplicate(Pattern, PatternSize);
    if (Entry->Pattern == NULL) {
        free(Entry);
        return FALSE;
    }

    Entry->PatternSize = PatternSize;
    INSERT_BEFORE(&(Entry->ListEntry), &(Set->PatternEntryList));
    return TRUE;
}

VOID
ShDestroyCasePatternList (
    PSHELL_NODE CaseNode
    )

/*++

Routine Description:

    This routine destroys the pattern sets in a case statement.

Arguments:

    CaseNode - Supplies a pointer to the case statement.

Return Value:

    None.

--*/

{

    PSHELL_CASE_PATTERN_ENTRY Entry;
    PSHELL_CASE_PATTERN_SET Set;

    assert(CaseNode->Type == ShellNodeCase);

    //
    // Loop through every set in the case.
    //

    while (LIST_EMPTY(&(CaseNode->U.Case.PatternList)) == FALSE) {
        Set = LIST_VALUE(CaseNode->U.Case.PatternList.Next,
                         SHELL_CASE_PATTERN_SET,
                         ListEntry);

        LIST_REMOVE(&(Set->ListEntry));
        if (Set->Action != NULL) {
            ShReleaseNode(Set->Action);
        }

        //
        // Loop through every entry in the set.
        //

        while (LIST_EMPTY(&(Set->PatternEntryList)) == FALSE) {
            Entry = LIST_VALUE(Set->PatternEntryList.Next,
                               SHELL_CASE_PATTERN_ENTRY,
                               ListEntry);

            LIST_REMOVE(&(Entry->ListEntry));
            free(Entry->Pattern);
            free(Entry);
        }

        free(Set);
    }

    return;
}

BOOL
ShAddComponentToCommand (
    PSHELL_NODE Command,
    PSTR Component,
    UINTN ComponentSize
    )

/*++

Routine Description:

    This routine adds a component to a simple command string.

Arguments:

    Command - Supplies a pointer to the simple command node.

    Component - Supplies a pointer to the component string, either the command
        or an argument.

    ComponentSize - Supplies the size of the component string in bytes
        including the null terminator.

Return Value:

    TRUE on success.

    FALSE on allocation failure.

--*/

{

    BOOL Result;
    PSHELL_NODE_SIMPLE_COMMAND SimpleCommand;

    assert(Command->Type == ShellNodeSimpleCommand);
    assert(ComponentSize != 0);

    SimpleCommand = &(Command->U.SimpleCommand);
    Result = ShStringAppend(&(SimpleCommand->Arguments),
                            &(SimpleCommand->ArgumentsSize),
                            &(SimpleCommand->ArgumentsBufferCapacity),
                            Component,
                            ComponentSize);

    return Result;
}

BOOL
ShIsStringQuoted (
    PSTR String
    )

/*++

Routine Description:

    This routine determines if any part of the given string is quoted, meaning
    it has a backslash, single quote, or double quote character in it.

Arguments:

    String - Supplies a pointer to the string to check.

Return Value:

    TRUE if the string has a quoting character in it.

    FALSE if the string is clean.

--*/

{

    while (*String != '\0') {
        if ((*String == SHELL_CONTROL_QUOTE) ||
            (*String == SHELL_CONTROL_ESCAPE)) {

            return TRUE;
        }

        String += 1;
    }

    return FALSE;
}

VOID
ShParseError (
    PSHELL Shell,
    PSHELL_NODE Node,
    PSTR Format,
    ...
    )

/*++

Routine Description:

    This routine prints a shell parse error to standard error.

Arguments:

    Shell - Supplies a pointer to the shell.

    Node - Supplies an optional pointer to the shell node being parsed.

    Format - Supplies the printf style format string.

    ... - Supplies the remaining arguments to the printf string.

Return Value:

    TRUE if the string has a quoting character in it.

    FALSE if the string is clean.

--*/

{

    va_list ArgumentList;
    ULONG LineNumber;

    if (Node != NULL) {
        LineNumber = Node->LineNumber;

    } else {
        LineNumber = Shell->Lexer.LineNumber;
    }

    fprintf(stderr, "sh: %d: ", LineNumber);
    va_start(ArgumentList, Format);
    vfprintf(stderr, Format, ArgumentList);
    va_end(ArgumentList);
    if (Shell->Lexer.TokenBufferSize != 0) {

        //
        // Make sure the buffer is null terminated, ideally not clobbering the
        // valid part of the string.
        //

        if (Shell->Lexer.TokenBufferSize < Shell->Lexer.TokenBufferCapacity) {
            Shell->Lexer.TokenBuffer[Shell->Lexer.TokenBufferSize] = '\0';

        } else {
            Shell->Lexer.TokenBuffer[Shell->Lexer.TokenBufferSize - 1] = '\0';
        }

        fprintf(stderr, " Token: %s.\n", Shell->Lexer.TokenBuffer);

    } else {
        fprintf(stderr, "\n");
    }

    return;
}

