// This switch follows more or less a DEA to this EBNF
        // COMMAND-EBNF := COMMAND

        // COMMAND      := (!ARGUMENT \s+ (ARGUMENT|\s)*)
        // ARGUMENT     := COMMAND|FREESTRING|QUOTESTRING
        // FREESTRING   := [^)]+
        // QUOTESTRING  := "[<anything but ", \" is ok>]+"

        public static AstNode ParseCommandRequest(string request, char commandChar = DefaultCommandChar, char delimeterChar = DefaultDelimeterChar)
        {
            AstCommand root   = null;
            var        comAst = new Stack <AstCommand>();
            var        build  = BuildStatus.ParseCommand;
            var        strb   = new StringBuilder();
            var        strPtr = new StringPtr(request);

            while (!strPtr.End)
            {
                AstCommand buildCom;
                switch (build)
                {
                case BuildStatus.ParseCommand:
                    // Got a command
                    buildCom = new AstCommand();
                    // Consume CommandChar if left over
                    if (strPtr.Char == commandChar)
                    {
                        strPtr.Next(commandChar);
                    }

                    if (root == null)
                    {
                        root = buildCom;
                    }
                    else
                    {
                        comAst.Peek().Parameter.Add(buildCom);
                    }
                    comAst.Push(buildCom);
                    build = BuildStatus.SelectParam;
                    break;

                case BuildStatus.SelectParam:
                    strPtr.SkipChar(delimeterChar);

                    if (strPtr.End)
                    {
                        build = BuildStatus.End;
                    }
                    else
                    {
                        switch (strPtr.Char)
                        {
                        case '"':
                            build = BuildStatus.ParseQuotedString;
                            //goto case BuildStatus.ParseQuotedString;
                            break;

                        case '(':
                            if (!strPtr.HasNext)
                            {
                                build = BuildStatus.ParseFreeString;
                            }
                            else if (strPtr.IsNext(commandChar))
                            {
                                strPtr.Next('(');
                                build = BuildStatus.ParseCommand;
                            }
                            else
                            {
                                build = BuildStatus.ParseFreeString;
                            }
                            break;

                        case ')':
                            if (comAst.Count <= 0)
                            {
                                build = BuildStatus.End;
                            }
                            else
                            {
                                comAst.Pop();
                                if (comAst.Count <= 0)
                                {
                                    build = BuildStatus.End;
                                }
                            }
                            strPtr.Next();
                            break;

                        default:
                            build = BuildStatus.ParseFreeString;
                            break;
                        }
                    }
                    break;

                case BuildStatus.ParseFreeString:
                    strb.Clear();

                    var valFreeAst = new AstValue();
                    using (strPtr.TrackNode(valFreeAst))
                    {
                        for (; !strPtr.End; strPtr.Next())
                        {
                            if ((strPtr.Char == '(' && strPtr.HasNext && strPtr.IsNext(commandChar)) ||
                                strPtr.Char == ')' ||
                                strPtr.Char == delimeterChar)
                            {
                                break;
                            }
                            strb.Append(strPtr.Char);
                        }
                    }
                    valFreeAst.Value = strb.ToString();
                    buildCom         = comAst.Peek();
                    buildCom.Parameter.Add(valFreeAst);
                    build = BuildStatus.SelectParam;
                    break;

                case BuildStatus.ParseQuotedString:
                    strb.Clear();

                    strPtr.Next('"');

                    var valQuoAst = new AstValue();
                    using (strPtr.TrackNode(valQuoAst))
                    {
                        bool escaped = false;
                        for (; !strPtr.End; strPtr.Next())
                        {
                            if (strPtr.Char == '\\')
                            {
                                escaped = true;
                            }
                            else if (strPtr.Char == '"')
                            {
                                if (escaped)
                                {
                                    strb.Length--;
                                }
                                else
                                {
                                    strPtr.Next(); break;
                                }
                                escaped = false;
                            }
                            else
                            {
                                escaped = false;
                            }
                            strb.Append(strPtr.Char);
                        }
                    }
                    valQuoAst.Value = strb.ToString();
                    buildCom        = comAst.Peek();
                    buildCom.Parameter.Add(valQuoAst);
                    build = BuildStatus.SelectParam;
                    break;

                case BuildStatus.End:
                    strPtr.JumpToEnd();
                    break;

                default: throw new InvalidOperationException();
                }
            }

            return(root);
        }
Exemple #2
0
        // This switch follows more or less a DEA to this EBNF
        // COMMAND-EBNF := <COMMAND> | $.*^

        // COMMAND      := '!' <ARGUMENT> (\s+ <ARGUMENT>)*
        // ARGUMENT     := '(' <COMMAND> ')'? | <FREESTRING> | <QUOTESTRING>
        // FREESTRING   := [^)]+
        // QUOTESTRING  := '"' [<anything but ", \" is ok>]* '"'

        public static AstNode ParseCommandRequest(string request, char commandChar = DefaultCommandChar, char delimeterChar = DefaultDelimeterChar)
        {
            AstCommand?root   = null;
            var        comAst = new Stack <AstCommand>();
            var        build  = BuildStatus.ParseCommand;
            var        strb   = new StringBuilder();
            var        strPtr = new StringPtr(request);

            var startTrim = request.AsSpan().TrimStart();

            if (startTrim.IsEmpty || startTrim[0] != commandChar)
            {
                return(new AstValue(request, StringType.FreeString)
                {
                    Length = request.Length,
                    Position = 0,
                    Value = request,
                });
            }

            while (!strPtr.End)
            {
                AstCommand buildCom;
                switch (build)
                {
                case BuildStatus.ParseCommand:
                    // Got a command
                    buildCom = new AstCommand(request);
                    // Consume CommandChar if left over
                    if (strPtr.Char == commandChar)
                    {
                        strPtr.Next(commandChar);
                    }

                    if (root is null)
                    {
                        root = buildCom;
                    }
                    else
                    {
                        comAst.Peek().Parameter.Add(buildCom);
                    }
                    comAst.Push(buildCom);
                    build = BuildStatus.SelectParam;
                    break;

                case BuildStatus.SelectParam:
                    strPtr.SkipChar(delimeterChar);

                    if (strPtr.End)
                    {
                        build = BuildStatus.End;
                    }
                    else
                    {
                        switch (strPtr.Char)
                        {
                        case '"':
                        case '\'':
                            build = BuildStatus.ParseQuotedString;
                            //goto case BuildStatus.ParseQuotedString;
                            break;

                        case '(':
                            if (!strPtr.HasNext)
                            {
                                build = BuildStatus.ParseFreeString;
                            }
                            else if (strPtr.IsNext(commandChar))
                            {
                                strPtr.Next('(');
                                build = BuildStatus.ParseCommand;
                            }
                            else
                            {
                                build = BuildStatus.ParseFreeString;
                            }
                            break;

                        case ')':
                            if (comAst.Count <= 0)
                            {
                                build = BuildStatus.End;
                            }
                            else
                            {
                                buildCom = comAst.Pop();
                                foreach (var param in buildCom.Parameter)
                                {
                                    if (param is AstValue astVal)
                                    {
                                        astVal.TailLength = strPtr.Index - param.Position;
                                    }
                                }
                                if (comAst.Count <= 0)
                                {
                                    build = BuildStatus.End;
                                }
                            }
                            strPtr.Next();
                            break;

                        default:
                            build = BuildStatus.ParseFreeString;
                            break;
                        }
                    }
                    break;

                case BuildStatus.ParseFreeString:
                    var valFreeAst = new AstValue(request, StringType.FreeString);
                    using (strPtr.TrackNode(valFreeAst))
                    {
                        for (; !strPtr.End; strPtr.Next())
                        {
                            if ((strPtr.Char == '(' && strPtr.HasNext && strPtr.IsNext(commandChar)) ||
                                strPtr.Char == ')' ||
                                strPtr.Char == delimeterChar)
                            {
                                break;
                            }
                        }
                    }
                    buildCom = comAst.Peek();
                    buildCom.Parameter.Add(valFreeAst);
                    build = BuildStatus.SelectParam;
                    break;

                case BuildStatus.ParseQuotedString:
                    strb.Clear();

                    char quoteChar;
                    if (strPtr.TryNext('"'))
                    {
                        quoteChar = '"';
                    }
                    else if (strPtr.TryNext('\''))
                    {
                        quoteChar = '\'';
                    }
                    else
                    {
                        throw new Exception("Parser error");
                    }

                    var valQuoAst = new AstValue(request, StringType.QuotedString);
                    using (strPtr.TrackNode(valQuoAst))
                    {
                        bool escaped = false;
                        for (; !strPtr.End; strPtr.Next())
                        {
                            if (strPtr.Char == '\\')
                            {
                                escaped = true;
                            }
                            else if (strPtr.Char == quoteChar)
                            {
                                if (!escaped)
                                {
                                    strPtr.Next();
                                    break;
                                }
                                else
                                {
                                    strb.Length--;
                                    escaped = false;
                                }
                            }
                            else
                            {
                                escaped = false;
                            }

                            strb.Append(strPtr.Char);
                        }
                    }
                    valQuoAst.Value = strb.ToString();
                    buildCom        = comAst.Peek();
                    buildCom.Parameter.Add(valQuoAst);
                    build = BuildStatus.SelectParam;
                    break;

                case BuildStatus.End:
                    strPtr.JumpToEnd();
                    break;

                default: throw new InvalidOperationException();
                }
            }

            return(root ?? throw new InvalidOperationException("No ast was built"));
        }