【问题标题】:Parsing S-Expression in C# (KiCAD PCB file)在 C# 中解析 S-Expression(KiCAD PCB 文件)
【发布时间】:2017-07-09 20:23:23
【问题描述】:

我正在编写一个 .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 标记位置未正确管理(无法在原始位置序列化)。原因很简单:虽然解析器正确处理子节点,因为它们以左括号开头,它只是忽略 在同一级别的值(即用白色分隔的值空格),例如 hide 选项。我该如何处理这种情况?我尝试了几种方法,但我遇到了一些堆栈溢出异常(我只是失去了对递归的控制)。

同时我定义了一个自定义类来处理节点(用来代替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);
        }
    }
}

【问题讨论】:

    标签: c# kicad


    【解决方案1】:

    这是一个常见的解析习语。在EBNF中

    节点::原子| "(" 列表 ")"

    列表 ::= 节点 |列表节点

    在 C# 中可以实现为抽象基类,以及节点、原子和列表的类。我在这里做了类似的事情https://github.com/bobc/eakit/tree/master/source/kicad_tools/SExpression

    【讨论】:

    • 谢谢,我会尽快检查您的代码。我看到您正在解决类似的问题...您正在尝试解析 KiCAD 板文件?
    • 是的,一般用于读/写 KiCad 原理图、电路板、符号和封装。
    猜你喜欢
    • 2016-05-29
    • 1970-01-01
    • 2023-03-24
    • 2016-03-30
    • 1970-01-01
    • 2022-08-10
    • 1970-01-01
    • 2011-12-31
    • 1970-01-01
    相关资源
    最近更新 更多