在 C# 中解析 S-Expression(KiCAD PCB 文件)
Parsing S-Expression in C# (KiCAD PCB file)
我正在编写 .NET API (C#) 来管理 KiCAD PCB 文件。根据文档 (here),它们的格式是一种S-Expression。我尝试重新使用一些 S-Expression 解析器,但由于多种原因它们无法满足我的需求,所以我决定编写一个,但我发现自己陷入困境。
作为第一次尝试,我写下了一个简单的函数,它递归地下降到文件结构并创建一个匹配结构的 System.Windows.Forms.TreeNode
层次结构(我使用 TreeNode
因为我使用 TreeView
组件来描述解析后的结构):
private TreeNode Parser(StreamReader srStream, TreeNode tnCurrentNode)
{
bool _string = false;
do
{
TreeNode _tnNode = null;
char _c;
_c = (char)srStream.Read();
if (_string)
{
tnCurrentNode.Text += _c;
if (_c == '"')
_string = false;
}
else
switch (_c)
{
case '(':
_tnNode = new TreeNode();
tnCurrentNode.Nodes.Add(Parser(srStream, _tnNode));
break;
case ')':
return tnCurrentNode;
case '\n':
case '\r':
break;
case '"':
tnCurrentNode.Text += _c;
_string = true;
break;
default:
tnCurrentNode.Text += _c;
break;
}
} while (!srStream.EndOfStream);
return tnCurrentNode;
}
之后我写了一个序列化程序来写回文件。
一切正常,但有一种情况我的解析器处理不当,我无法找到合适的解决方案:
(fp_text value V23105 (at -2 0 180) (layer F.SilkS) hide
(effects (font (size 1 1) (thickness 0.25)))
)
隐藏令牌位置未正确管理(无法在原始位置序列化)。原因很简单:虽然解析器正确处理了子节点,但由于它们以左括号开头,它只是 忽略 处于同一级别的值 (即由空格分隔的值)例如 hide 选项。我该如何处理这种情况?我尝试了几种方法,但我只是遇到了一些堆栈溢出异常(我只是失去了对递归的控制)。
与此同时,我定义了一个自定义 class 来处理节点(用于代替 TreeNode
):
public class KiCADNode
{
public string Value { get; set; }
public NodeType Type { get; set; }
private readonly List<KiCADNode> _Nodes = new List<KiCADNode>();
public ICollection<KiCADNode> Nodes { get { return _Nodes; } }
public static implicit operator TreeNode(KiCADNode node)
{
if (node == null)
return null;
TreeNode _treenode = new TreeNode();
_treenode.Text = node.ToString();
foreach (KiCADNode _node in node._Nodes)
_treenode.Nodes.Add((TreeNode)_node);
return _treenode;
}
public override string ToString()
{
StringBuilder _sb = new StringBuilder();
if (Type == NodeType.List)
_sb.Append('(');
_sb.Append(Value);
if (Type == NodeType.Atom)
_sb.Append(' ');
if (Type == NodeType.List)
_sb.Append(')');
return _sb.ToString();
}
public KiCADNode()
{
Type = NodeType.Atom;
}
public KiCADNode(NodeType type)
{
Type = type;
}
public KiCADNode(string value) : this()
{
Value = value;
}
public KiCADNode(string value, NodeType type) : this(value)
{
Type = type;
}
private static KiCADNode Parse(StreamReader input)
{
KiCADNode _node = new KiCADNode("PCB");
return Parser(input, _node);
}
private static KiCADNode Parser(StreamReader input, KiCADNode current_node)
{
bool _string = false;
while (!input.EndOfStream)
{
KiCADNode _new_node = null;
char _c;
_c = (char)input.Read();
if (_string)
{
current_node.Value += _c;
if (_c == '"')
_string = false;
}
else
switch (_c)
{
case '(':
_new_node = new KiCADNode(NodeType.List);
current_node.Nodes.Add(Parser(input, _new_node));
break;
case ')':
return current_node;
case '\n':
case '\r':
break;
case '"':
current_node.Value += _c;
_string = true;
break;
default:
current_node.Value += _c;
break;
}
}
return current_node;
}
public static KiCADNode Parse(string filename)
{
if (!File.Exists(filename))
return null;
using (StreamReader _input = new StreamReader(filename))
{
return Parse(_input);
}
}
}
这是一个常见的解析习语。在 EBNF
node :: atom | "(" list ")"
list ::= node | list node
在 C# 中可以实现为抽象基础 class,以及用于节点、原子和列表的 class。我在这里做了类似的事情 https://github.com/bobc/eakit/tree/master/source/kicad_tools/SExpression
我正在编写 .NET API (C#) 来管理 KiCAD PCB 文件。根据文档 (here),它们的格式是一种S-Expression。我尝试重新使用一些 S-Expression 解析器,但由于多种原因它们无法满足我的需求,所以我决定编写一个,但我发现自己陷入困境。
作为第一次尝试,我写下了一个简单的函数,它递归地下降到文件结构并创建一个匹配结构的 System.Windows.Forms.TreeNode
层次结构(我使用 TreeNode
因为我使用 TreeView
组件来描述解析后的结构):
private TreeNode Parser(StreamReader srStream, TreeNode tnCurrentNode)
{
bool _string = false;
do
{
TreeNode _tnNode = null;
char _c;
_c = (char)srStream.Read();
if (_string)
{
tnCurrentNode.Text += _c;
if (_c == '"')
_string = false;
}
else
switch (_c)
{
case '(':
_tnNode = new TreeNode();
tnCurrentNode.Nodes.Add(Parser(srStream, _tnNode));
break;
case ')':
return tnCurrentNode;
case '\n':
case '\r':
break;
case '"':
tnCurrentNode.Text += _c;
_string = true;
break;
default:
tnCurrentNode.Text += _c;
break;
}
} while (!srStream.EndOfStream);
return tnCurrentNode;
}
之后我写了一个序列化程序来写回文件。 一切正常,但有一种情况我的解析器处理不当,我无法找到合适的解决方案:
(fp_text value V23105 (at -2 0 180) (layer F.SilkS) hide
(effects (font (size 1 1) (thickness 0.25)))
)
隐藏令牌位置未正确管理(无法在原始位置序列化)。原因很简单:虽然解析器正确处理了子节点,但由于它们以左括号开头,它只是 忽略 处于同一级别的值 (即由空格分隔的值)例如 hide 选项。我该如何处理这种情况?我尝试了几种方法,但我只是遇到了一些堆栈溢出异常(我只是失去了对递归的控制)。
与此同时,我定义了一个自定义 class 来处理节点(用于代替 TreeNode
):
public class KiCADNode
{
public string Value { get; set; }
public NodeType Type { get; set; }
private readonly List<KiCADNode> _Nodes = new List<KiCADNode>();
public ICollection<KiCADNode> Nodes { get { return _Nodes; } }
public static implicit operator TreeNode(KiCADNode node)
{
if (node == null)
return null;
TreeNode _treenode = new TreeNode();
_treenode.Text = node.ToString();
foreach (KiCADNode _node in node._Nodes)
_treenode.Nodes.Add((TreeNode)_node);
return _treenode;
}
public override string ToString()
{
StringBuilder _sb = new StringBuilder();
if (Type == NodeType.List)
_sb.Append('(');
_sb.Append(Value);
if (Type == NodeType.Atom)
_sb.Append(' ');
if (Type == NodeType.List)
_sb.Append(')');
return _sb.ToString();
}
public KiCADNode()
{
Type = NodeType.Atom;
}
public KiCADNode(NodeType type)
{
Type = type;
}
public KiCADNode(string value) : this()
{
Value = value;
}
public KiCADNode(string value, NodeType type) : this(value)
{
Type = type;
}
private static KiCADNode Parse(StreamReader input)
{
KiCADNode _node = new KiCADNode("PCB");
return Parser(input, _node);
}
private static KiCADNode Parser(StreamReader input, KiCADNode current_node)
{
bool _string = false;
while (!input.EndOfStream)
{
KiCADNode _new_node = null;
char _c;
_c = (char)input.Read();
if (_string)
{
current_node.Value += _c;
if (_c == '"')
_string = false;
}
else
switch (_c)
{
case '(':
_new_node = new KiCADNode(NodeType.List);
current_node.Nodes.Add(Parser(input, _new_node));
break;
case ')':
return current_node;
case '\n':
case '\r':
break;
case '"':
current_node.Value += _c;
_string = true;
break;
default:
current_node.Value += _c;
break;
}
}
return current_node;
}
public static KiCADNode Parse(string filename)
{
if (!File.Exists(filename))
return null;
using (StreamReader _input = new StreamReader(filename))
{
return Parse(_input);
}
}
}
这是一个常见的解析习语。在 EBNF
node :: atom | "(" list ")"
list ::= node | list node
在 C# 中可以实现为抽象基础 class,以及用于节点、原子和列表的 class。我在这里做了类似的事情 https://github.com/bobc/eakit/tree/master/source/kicad_tools/SExpression