Разбор S-выражения в C# (файл PCB KiCAD)

Я пишу .NET API (C#) для управления файлами печатных плат KiCAD. Их формат в соответствии с документами ( здесь) является своего рода 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. Как мне справиться с таким состоянием? Я пытался несколькими способами, но я только что получил ряд исключений переполнения стека (я просто потерял контроль над рекурсией).

Тем временем я определил пользовательский класс для обработки узлов (который будет использоваться вместо 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);
        }
    }
}

1 ответ

Это обычная идиома синтаксического анализа. В ЕБНФ

узел:: атом | "(" список ")"

список::= узел | узел списка

который в C# можно реализовать как абстрактный базовый класс и класс для узла, атома и списка. Я сделал что-то подобное здесь https://github.com/bobc/eakit/tree/master/source/kicad_tools/SExpression

Другие вопросы по тегам