@ T.S。在評論中指出,除了教育目的,很少有重新發明輪子的好理由。當談到寫作解析器,正是這些極端案例,這使得它很難得到正確:格式
- 驗證,更具體的返回友好 消息給用戶
- 優化代碼的性能,分配,併發性,...
這就是說,在C#7支持模式匹配的情況下,基本的管道應該很容易實現。第一站,Node
類,加入初始化。
public class Node
{
public Node(string name)
{
Name = name;
InnerNodes = new List<Node>();
}
public string Name { get; }
public string Contents { get; set; }
public List<Node> InnerNodes { get; }
}
接下來,爲我們的語法的不同部分創建包裝。
internal abstract class Token
{
}
internal class OpenNodeToken : Token
{
public OpenNodeToken(string name) { Name = name; }
public string Name { get; }
}
internal class CloseNodeToken : Token
{
}
internal class ContentToken : Token
{
public ContentToken(string text) { Text = text; }
public string Text { get; }
}
還有一個幫助器類,用於將輸入字符串轉換爲令牌序列。
internal static class Tokenizer
{
public static IEnumerable<Token> Scan (string expression)
{
var words = new Queue<string>
(expression.Split(new[] { ' ', '\n', '\r', '\t' }, StringSplitOptions.RemoveEmptyEntries));
while (words.Any())
{
string word = words.Dequeue();
switch (word)
{
case "id":
yield return new OpenNodeToken(words.Dequeue());
words.Dequeue();
break;
case "}":
yield return new CloseNodeToken();
break;
default:
yield return new ContentToken(word);
break;
}
}
}
}
我爲了離隊多串一氣呵成的id
情況下使用的Queue
。
最後,解析器,其中模式匹配允許簡潔的代碼。
public static class Parser
{
private static Node currNode;
private static Stack<Node> prevNodes;
private static IEnumerable<Token> tokens;
static Parser()
{
prevNodes = new Stack<Node>();
}
public static Node Deserialize(string input)
{
tokens = Tokenizer.Scan(input);
if (!(tokens.FirstOrDefault() is OpenNodeToken rootToken))
throw new FormatException("Missing root node");
currNode = new Node(rootToken.Name);
foreach(Token token in tokens.Skip(1))
{
switch (token)
{
case ContentToken c:
string s = string.IsNullOrEmpty(currNode.Contents) ? c.Text : " " + c.Text;
currNode.Contents += s;
break;
case OpenNodeToken n:
prevNodes.Push(currNode);
currNode = new Node(n.Name);
break;
case CloseNodeToken c:
if (prevNodes.Any())
{
Node childNode = currNode;
currNode = prevNodes.Pop();
currNode.InnerNodes.Add(childNode);
}
break;
default: throw new NotImplementedException(token.GetType().Name);
}
}
return currNode;
}
}
下面我們用一個Stack
解析孩子之前父節點推入。一旦孩子的右括號被滿足,我們彈出堆棧的父親並將孩子添加到它的集合中。
如上所述,一個體面的解析器也應該覆蓋角落案例,我已經將這些實現的樂趣留給了讀者。爲了測試解析器,我在下面設置了控制檯項目。
namespace MySimpleParser
{
class Program
{
public static void Main(string[] args)
{
string s = GetInput();
try
{
Node root = Parser.Deserialize(s);
PrintBranch(root, 1);
}
catch (Exception ex)
{
Console.WriteLine($"{ex.GetType().Name}: {ex.Message}");
}
Console.ReadLine();
}
internal static string GetInput()
{
return @"id i {
any data; any [con=tent]
id j {
any inner data
}
id k {
bla
id m {
any
}
thing
}
}";
}
internal static void PrintNode(Node n, int depth)
{
string indent = new string('-', 3 * depth);
Console.WriteLine($"{indent} Name: {n.Name}");
Console.WriteLine($"{indent} Contents: {n.Contents}");
Console.WriteLine($"{indent} Child Nodes: {n.InnerNodes.Count}");
}
internal static void PrintBranch(Node root, int depth)
{
PrintNode(root, depth);
foreach (Node child in root.InnerNodes) PrintBranch(child, depth + 1);
}
}
}
輸出
--- Name: i
--- Contents: any data; any [con=tent]
--- Child Nodes: 2
------ Name: j
------ Contents: any inner data
------ Child Nodes: 0
------ Name: k
------ Contents: bla thing
------ Child Nodes: 1
--------- Name: m
--------- Contents: any
--------- Child Nodes: 0
使用遞歸算法。 – jdweng
第二個想法寫一個解析器......爲什麼要用這個?使用JSON或YAML。解析器已經寫入並可用 –
這裏的語法不僅僅是'id NAME {CONTENT}'。您需要提供如何解析此數據的完整詳細信息。 – Enigmativity