// 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); }
// 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")); }