Разбор 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