private ParserState ParseFunction(ParserState state, string funcName)
        {
            var builder = Utils.GetTempStringBuilder();
            var args    = new List <Node>();

            while (!(state.Char() == Sym.cLineBreak || state.Done()))
            {
                if (state.MatchAhead(Sym.CodeOpen))
                {
                    var codeState = new ParserState(new BlockNode(BlockMode.Code), state.ConsumeBlock(Sym.CodeOpen, Sym.CodeClose));
                    codeState = ParseCode(codeState);
                    args.Add(codeState.RootNode);
                    builder.Length = 0;
                }
                else if (state.MatchAhead(Sym.String))
                {
                    var str = state.ConsumeBlock(Sym.String, Sym.String);
                    args.Add(new LiteralNode(str));
                    builder.Length = 0;
                }
                else if (state.Char() == ' ' && builder.Length > 0)
                {
                    args.Add(StringToValueNode(builder.ToString().Trim()));
                    builder.Length = 0;
                }
                else
                {
                    builder.Append(state.Char());
                }
                state.Step();
            }

            if (builder.Length > 0)
            {
                args.Add(StringToValueNode(builder.ToString().Trim()));
                builder.Length = 0;
            }
            Utils.Release(builder);

            state.CurNode.AddChild(new FuncNode(funcName, args.ToArray()));
            return(state);
        }
        private ParserState ParseCode(ParserState state)
        {
            string name;

            while (!state.Done())
            {
                if (char.IsWhiteSpace(state.Char()))
                {
                    state.Step();
                }
                else if (state.MatchAhead(Sym.CodeOpen))
                {
                    state = ParseCodeBlock(state);
                }
                // NOTE: nested dialog blocks disabled for now
                //else if (state.MatchAhead(Sym.DialogOpen))
                //{
                //    state = ParseDialogBlock(state);
                //}
                else if (state.Char() == Sym.cList && state.Peek("").IndexOf('?') >= 0)
                {
                    state = ParseIf(state);
                }
                else if (HasFunction(name = state.Peek(" ")))
                {
                    state.Step(name.Length);
                    state = ParseFunction(state, name);
                }
                else if (IsSequence(name = state.Peek(" \n")))
                {
                    state.Step(name.Length);
                    state = ParseSequence(state, name);
                }
                else
                {
                    state = ParseExpression(state);
                }
            }
            return(state);
        }
        private ParserState ParseSequence(ParserState state, string sequenceType)
        {
            bool isNewLine = false;
            List <StringBuilder> itemStrings = new List <StringBuilder>();
            int curItemIndex   = -1; //-1 indicates not reading an item yet
            int codeBlockCount = 0;

            while (!state.Done())
            {
                if (state.Char() == Sym.cCodeOpen)
                {
                    codeBlockCount++;
                }
                else if (state.Char() == Sym.cCodeClose)
                {
                    codeBlockCount--;
                }

                bool isWhiteSpace          = (state.Char() == ' ' || state.Char() == '\t');
                bool isSkippableWhitespace = isNewLine && isWhiteSpace;
                bool isNewListItem         = isNewLine && (codeBlockCount <= 0) && state.Char() == Sym.cList;

                if (isNewListItem)
                {
                    curItemIndex++;
                    itemStrings.Add(Utils.GetTempStringBuilder());
                }
                else if (curItemIndex > -1)
                {
                    if (!isSkippableWhitespace)
                    {
                        itemStrings[curItemIndex].Append(state.Char());
                    }
                }

                isNewLine = state.Char() == Sym.cLineBreak || isSkippableWhitespace || isNewListItem;

                state.Step();
            }

            var options = new Node[itemStrings.Count];

            for (int i = 0; i < itemStrings.Count; i++)
            {
                var str         = Utils.Release(itemStrings[i]);
                var dialogState = new ParserState(new BlockNode(BlockMode.Dialog, false), str);
                dialogState = ParseDialog(dialogState);
                options[i]  = dialogState.RootNode;
            }

            switch (sequenceType)
            {
            case "sequence":
                state.CurNode.AddChild(new SequenceNode(options));
                break;

            case "cycle":
                state.CurNode.AddChild(new CycleNode(options));
                break;

            case "shuffle":
                state.CurNode.AddChild(new ShuffleNode(options));
                break;
            }

            return(state);
        }
        private ParserState ParseIf(ParserState state)
        {
            List <StringBuilder> conditionStrings = new List <StringBuilder>();
            List <StringBuilder> resultStrings    = new List <StringBuilder>();
            var  curIndex        = -1;
            bool isNewLine       = true;
            bool isConditionDone = false;
            int  codeBlockCount  = 0;

            while (!state.Done())
            {
                if (state.Char() == Sym.cCodeOpen)
                {
                    codeBlockCount++;
                }
                else if (state.Char() == Sym.cCodeClose)
                {
                    codeBlockCount--;
                }

                bool isWhiteSpace          = (state.Char() == ' ' || state.Char() == '\t');
                bool isSkippableWhitespace = isNewLine && isWhiteSpace;
                bool isNewListItem         = isNewLine && (codeBlockCount <= 0) && state.Char() == Sym.cList;

                if (isNewListItem)
                {
                    curIndex++;
                    isConditionDone = false;
                    conditionStrings.Add(Utils.GetTempStringBuilder());
                    resultStrings.Add(Utils.GetTempStringBuilder());
                }
                else if (curIndex > -1)
                {
                    if (!isConditionDone)
                    {
                        if (state.Char() == '?' || state.Char() == Sym.cLineBreak)
                        {
                            isConditionDone = true;
                        }
                        else
                        {
                            conditionStrings[curIndex].Append(state.Char());
                        }
                    }
                    else
                    {
                        if (!isSkippableWhitespace)
                        {
                            resultStrings[curIndex].Append(state.Char());
                        }
                    }
                }

                isNewLine = (state.Char() == Sym.cLineBreak) || isSkippableWhitespace || isNewListItem;

                state.Step();
            }

            var conditions = new Node[conditionStrings.Count];

            for (int i = 0; i < conditionStrings.Count; i++)
            {
                var str = Utils.Release(conditionStrings[i]);
                if (str != null)
                {
                    str = str.Trim();
                }
                if (str == "else")
                {
                    conditions[i] = new ElseNode();
                }
                else
                {
                    conditions[i] = CreateExpression(str);
                }
            }

            var results = new Node[resultStrings.Count];

            for (int i = 0; i < resultStrings.Count; i++)
            {
                var str = Utils.Release(resultStrings[i]);
                if (str != null)
                {
                    str = str.Trim();
                }
                var dialogState = new ParserState(new BlockNode(BlockMode.Dialog), str);
                dialogState = ParseDialog(dialogState);
                results[i]  = dialogState.RootNode;
            }

            state.CurNode.AddChild(new IfNode(conditions, results, false));

            return(state);
        }
        private ParserState ParseDialog(ParserState state)
        {
            bool hasBlock    = false;
            bool hasDialog   = false;
            bool isFirstLine = true;

            var builder = Utils.GetTempStringBuilder();

            while (!state.Done())
            {
                if (state.MatchAhead(Sym.CodeOpen))
                {
                    if (AddTextNode(builder, state))
                    {
                        hasDialog = true;
                    }
                    state = ParseCodeBlock(state);

                    int len = state.CurNode.Children.Count;
                    if (len > 0 && state.CurNode.Children[len - 1] is NodeParent)
                    {
                        var block = state.CurNode.Children[len - 1] as NodeParent;
                        if (block.IsMultilineListBlock())
                        {
                            hasDialog = true;
                        }
                    }

                    hasBlock = true;
                }
                // NOTE: nested dialog blocks disabled for now
                //else if(state.MatchAhead(Sym.DialogOpen))
                //{
                //    if (AddTextNode(builder, state)) hasDialog = true;
                //    state = ParseDialogBlock(state);
                //    hasBlock = true;
                //}
                else
                {
                    if (state.MatchAhead(Sym.LineBreak))
                    {
                        if (AddTextNode(builder, state))
                        {
                            hasDialog = true;
                        }

                        bool isLastLine         = (state.Index + 1) == state.Count;
                        bool isEmptyLine        = !hasBlock && !hasDialog;
                        bool isValidEmptyLine   = isEmptyLine && !(isFirstLine || isLastLine);
                        bool shouldAddLineBreak = (hasDialog || isValidEmptyLine) && !isLastLine; // last clause is a hack (but it works - why?)
                        if (shouldAddLineBreak)
                        {
                            state.CurNode.AddChild(new FuncNode(FUNC_BR));
                        }

                        isFirstLine = false;
                        hasBlock    = false;
                        hasDialog   = false;

                        builder.Length = 0;
                    }
                    else
                    {
                        builder.Append(state.Char());
                    }

                    state.Step();
                }
            }
            AddTextNode(builder, state);

            return(state);
        }