接上篇:
这一节写C#程序,它做下面几件事:
(1)指令转逻辑表达式
(2)逻辑表达式放入二叉树,以方便指令仿真时进行解析。
下面是生成的二叉树效果。
源码如下:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Diagnostics; using System.Drawing; using System.Linq; using System.Reflection.Emit; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace WindowsFormsApp1 { public partial class Form1 : Form { private Bitmap bufferBitmap; private Graphics bufferGraphics; private int currentX; // 当前横坐标计数器 private BTree root; // 新增:表达式树根节点作为成员变量 public Form1() { InitializeComponent(); InitializeBuffer(); this.button1.Text = "生成表达式树"; this.Text = "PLC梯形图表达式树可视化"; panel1.AutoScroll = true; } private void InitializeBuffer() { bufferBitmap = new Bitmap(panel1.Width, panel1.Height); bufferGraphics = Graphics.FromImage(bufferBitmap); bufferGraphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; } protected override void OnFormClosing(FormClosingEventArgs e) { base.OnFormClosing(e); if (bufferGraphics != null) { bufferGraphics.Dispose(); } if (bufferBitmap != null) { bufferBitmap.Dispose(); } } private void button1_Click(object sender, EventArgs e) { // 清空文本框 richTextBox1.Clear(); // 清空绘图缓冲区 bufferGraphics.Clear(Color.White); // 梯形图指令 List<string> ladderInstructions = new List<string> { "LD X0", "LD X1", "AND X2", "ORB", "OR X3", "AND M0", "OUT Y0" }; try { // 转换为二叉树表达式 string expression = ConvertLadderToExpression(ladderInstructions); // 拼接ladderInstructions为多行字符串 string ladderText = string.Join("\r\n", ladderInstructions); richTextBox1.AppendText(ladderText + "\r\n"); richTextBox1.AppendText($"转换后的表达式: {expression}\r\n\r\n"); var parser = new ExpressionParser(); root = parser.BuildExpressionTree(expression); // 赋值到成员变量 // 打印树结构 PrintTreeStructure(root, 0); //PrintTreeStructure2(root, 0); // 触发panel1重绘 panel1.Invalidate(); } catch (Exception ex) { MessageBox.Show($"发生错误: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); } } /// <summary> /// 将PLC梯形图指令列表转换为表达式字符串 /// </summary> /// <param name="plcInstructions">PLC梯形图指令列表,如 ["LD X0", "AND X1", "OR X2"]</param> /// <returns>转换后的表达式字符串,如 "X0*X1+X2"</returns> /// <remarks> /// 转换规则: /// 1. LD指令将操作数压入栈 /// 2. AND指令将栈顶元素与操作数进行与运算(*) /// 3. ORB指令将栈顶两个元素进行或运算(+) /// 4. OR指令将栈顶元素与操作数进行或运算(+) /// 5. OUT/END指令不做处理 /// </remarks> public string ConvertLadderToExpression(List<string> plcInstructions) { var stack = new Stack<string>(); foreach (var instruction in plcInstructions) { var parts = instruction.Split(' '); var op = parts[0]; var operand = parts.Length > 1 ? parts[1] : ""; switch (op) { case "LD": stack.Push(operand); break; case "AND": if (stack.Count == 0) throw new System.InvalidOperationException("Invalid AND operation"); stack.Push($"{WrapIfNeeded(stack.Pop(), "AND")}*{operand}"); break; case "ORB": if (stack.Count < 2) throw new System.InvalidOperationException("Invalid ORB operation"); var a = stack.Pop(); var b = stack.Pop(); stack.Push($"{WrapIfNeeded(b, "OR")}+{WrapIfNeeded(a, "OR")}"); break; case "OR": if (stack.Count == 0) throw new System.InvalidOperationException("Invalid OR operation"); stack.Push($"{WrapIfNeeded(stack.Pop(), "OR")}+{operand}"); break; case "OUT": case "END": break; } } return stack.Count > 0 ? stack.Pop() : ""; } /// <summary> /// 根据运算符优先级决定是否需要给表达式添加括号 /// </summary> /// <param name="expr">要处理的表达式</param> /// <param name="operation">当前操作类型("AND"或"OR")</param> /// <returns>处理后的表达式,必要时会添加括号</returns> /// <remarks> /// 当进行AND运算(*)且表达式中包含OR运算(+)时,需要添加括号保证运算优先级 /// </remarks> private string WrapIfNeeded(string expr, string operation) { // 当表达式包含低优先级操作符时加括号 if (operation == "AND" && expr.Contains("+")) return $"({expr})"; return expr; } void PrintTreeStructure(BTree root, int _) { currentX = 0; int xSpacing = 80; // 横向间距 int ySpacing = 80; // 纵向间距 var posRoot = AssignPositions(root, 0, xSpacing, ySpacing); DrawTree(posRoot); } void PrintTreeStructure2(BTree node, int level) { if (node == null) return; // 打印当前节点信息 string indent = new string(' ', level * 4); richTextBox1.AppendText($"{indent}节点层级: {level}\r\n"); richTextBox1.AppendText($"{indent}节点值: {node.Value} \r\n"); richTextBox1.AppendText($"{indent}左子节点: {node.Left?.Value ?? "null"}\r\n"); richTextBox1.AppendText($"{indent}右子节点: {node.Right?.Value ?? "null"}\r\n"); richTextBox1.AppendText($"{indent}----------------\r\n"); // 递归打印子树 PrintTreeStructure2(node.Left, level + 1); PrintTreeStructure2(node.Right, level + 1); } // 递归分配每个节点的坐标 private TreeNodePosition AssignPositions(BTree node, int level, int xSpacing, int ySpacing) { if (node == null) return null; var left = AssignPositions(node.Left, level + 1, xSpacing, ySpacing); int x = currentX * xSpacing + 60; // 60是左边距 int y = 50 + level * ySpacing; var pos = new TreeNodePosition { Node = node, X = x, Y = y, Left = left }; currentX++; var right = AssignPositions(node.Right, level + 1, xSpacing, ySpacing); pos.Right = right; return pos; } private void DrawTree(TreeNodePosition pos) { if (pos == null) return; using (Pen pen = new Pen(Color.Black)) using (Brush brush = new SolidBrush(Color.White)) using (Brush textBrush = new SolidBrush(Color.Black)) { // 画连线 if (pos.Left != null) bufferGraphics.DrawLine(pen, pos.X, pos.Y, pos.Left.X, pos.Left.Y); if (pos.Right != null) bufferGraphics.DrawLine(pen, pos.X, pos.Y, pos.Right.X, pos.Right.Y); // 画节点 bufferGraphics.FillEllipse(brush, pos.X - 20, pos.Y - 12, 40, 25); bufferGraphics.DrawEllipse(pen, pos.X - 20, pos.Y - 12, 40, 25); StringFormat format = new StringFormat(); format.Alignment = StringAlignment.Center; format.LineAlignment = StringAlignment.Center; bufferGraphics.DrawString(pos.Node.Value, this.Font, textBrush, pos.X, pos.Y, format); } DrawTree(pos.Left); DrawTree(pos.Right); } private int CalcSubtreeWidth(BTree node) { if (node == null) return 0; if (node.Left == null && node.Right == null) return 1; return CalcSubtreeWidth(node.Left) + CalcSubtreeWidth(node.Right); } private int AssignPositionsAuto(BTree node, int level, int x, int y, int xSpacing, int ySpacing, Dictionary<BTree, Point> posDict) { if (node == null) return x; int left = AssignPositionsAuto(node.Left, level + 1, x, y + ySpacing, xSpacing, ySpacing, posDict); int curX = left; posDict[node] = new Point(curX, y); int right = AssignPositionsAuto(node.Right, level + 1, left + 1, y + ySpacing, xSpacing, ySpacing, posDict); return right; } private int AssignPositionsSmart(BTree node, int level, int xSpacing, int ySpacing, Dictionary<BTree, Point> posDict, ref int leafIndex) { if (node == null) return -1; int left = AssignPositionsSmart(node.Left, level + 1, xSpacing, ySpacing, posDict, ref leafIndex); int myX; if (node.Left == null && node.Right == null) { myX = leafIndex++; } else { int right = AssignPositionsSmart(node.Right, level + 1, xSpacing, ySpacing, posDict, ref leafIndex); if (node.Left != null && node.Right != null) myX = (left + right) / 2; else if (node.Left != null) myX = left + 1; else myX = right - 1; } posDict[node] = new Point(myX * xSpacing + 60, level * ySpacing + 50); return myX; } private int AssignPositionsBetter(BTree node, int level, int x, int y, int xSpacing, int ySpacing, Dictionary<BTree, Point> posDict) { if (node == null) return 0; int leftWidth = AssignPositionsBetter(node.Left, level + 1, x, y + ySpacing, xSpacing, ySpacing, posDict); int rightWidth = AssignPositionsBetter(node.Right, level + 1, x + leftWidth * xSpacing, y + ySpacing, xSpacing, ySpacing, posDict); int myWidth = Math.Max(1, leftWidth + rightWidth); // 计算当前节点的横坐标 int nodeX; if (leftWidth == 0 && rightWidth == 0) { nodeX = x; } else if (leftWidth == 0) { nodeX = x + (rightWidth * xSpacing) / 2; } else if (rightWidth == 0) { nodeX = x + (leftWidth * xSpacing) / 2; } else { nodeX = x + (leftWidth * xSpacing) / 2; } posDict[node] = new Point(nodeX + 60, y + 50); // 60为左边距,50为上边距 return myWidth; } private void panel1_Paint(object sender, PaintEventArgs e) { if (root == null) return; var posDict = new Dictionary<BTree, Point>(); int ySpacing = 80, xSpacing = 100; int totalWidth = AssignPositionsBetter(root, 0, 0, 0, xSpacing, ySpacing, posDict); // 计算滚动区域宽度,确保最右侧节点不会被遮挡 int nodeWidth = 40; int extraMargin = 80; panel1.AutoScrollMinSize = new Size(Math.Max(totalWidth * xSpacing + nodeWidth + extraMargin, panel1.Width), (GetTreeDepth(root) + 1) * ySpacing); // 偏移量 Point scroll = panel1.AutoScrollPosition; DrawTreeWithDict(e.Graphics, root, posDict, scroll); } private void DrawTreeWithDict(Graphics g, BTree node, Dictionary<BTree, Point> posDict, Point scroll) { if (node == null) return; var p = posDict[node]; int x = p.X + scroll.X + 60; int y = p.Y + scroll.Y; // 画线 if (node.Left != null) { var lp = posDict[node.Left]; g.DrawLine(Pens.Black, x, y, lp.X + scroll.X + 60, lp.Y + scroll.Y); } if (node.Right != null) { var rp = posDict[node.Right]; g.DrawLine(Pens.Black, x, y, rp.X + scroll.X + 60, rp.Y + scroll.Y); } // 画节点 g.FillEllipse(Brushes.White, x - 20, y - 12, 40, 25); g.DrawEllipse(Pens.Black, x - 20, y - 12, 40, 25); StringFormat format = new StringFormat { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center }; g.DrawString(node.Value, this.Font, Brushes.Black, x, y, format); DrawTreeWithDict(g, node.Left, posDict, scroll); DrawTreeWithDict(g, node.Right, posDict, scroll); } // 新增:递归获取树的深度 private int GetTreeDepth(BTree node) { if (node == null) return 0; return 1 + Math.Max(GetTreeDepth(node.Left), GetTreeDepth(node.Right)); } } /// <summary> /// 二叉树节点类,用于构建表达式树结构 /// </summary> /// <remarks> /// 功能说明: /// 1. 存储节点值(Value) - 可以是运算符(+、*)或操作数(X0,X1等) /// 2. 包含左右子节点引用(Left, Right) /// 3. 用于构建和表示表达式树的层次结构 /// </remarks> public class BTree { public string Value { get; set; } public BTree Left { get; set; } public BTree Right { get; set; } public BTree(string value) { Value = value; Left = null; Right = null; } } /// <summary> /// 表达式解析器,用于将字符串表达式转换为二叉树结构 /// </summary> /// <remarks> /// 功能说明: /// 1. 支持解析包含运算符(+、*)和括号的表达式 /// 2. 支持变量名(字母数字和下划线组合) /// 3. 自动处理运算符优先级 /// 4. 支持嵌套括号表达式 /// /// 使用示例: /// var parser = new ExpressionParser(); /// BTree tree = parser.BuildExpressionTree("(X0+X1)*X2"); /// </remarks> public class ExpressionParser { public BTree BuildExpressionTree(string expression) { var tokens = Tokenize(expression); return ParseExpression(tokens.GetEnumerator()); } private List<string> Tokenize(string input) { List<string> tokens = new List<string>(); input = input.Replace(" ", ""); int pos = 0; while (pos < input.Length) { if (input[pos] == '(' || input[pos] == ')' || input[pos] == '+' || input[pos] == '*') { tokens.Add(input[pos].ToString()); pos++; } else if (char.IsLetterOrDigit(input[pos])) { int start = pos; while (pos < input.Length && (char.IsLetterOrDigit(input[pos]) || input[pos] == '_')) { pos++; } tokens.Add(input.Substring(start, pos - start)); } else { pos++; } } return tokens; } private BTree ParseExpression(IEnumerator<string> tokenEnumerator) { if (!tokenEnumerator.MoveNext()) return null; Stack<BTree> nodeStack = new Stack<BTree>(); Stack<string> opStack = new Stack<string>(); do { string token = tokenEnumerator.Current; if (token == "(") { opStack.Push(token); } else if (token == ")") { while (opStack.Peek() != "(") { PushOperator(nodeStack, opStack.Pop()); } opStack.Pop(); } else if (IsOperator(token)) { while (opStack.Count > 0 && GetPrecedence(opStack.Peek()) > GetPrecedence(token)) { PushOperator(nodeStack, opStack.Pop()); } opStack.Push(token); } else { nodeStack.Push(new BTree(token)); } } while (tokenEnumerator.MoveNext()); while (opStack.Count > 0) { PushOperator(nodeStack, opStack.Pop()); } return nodeStack.Count > 0 ? nodeStack.Pop() : null; } private bool IsOperator(string token) => token == "+" || token == "*"; private int GetPrecedence(string op) { switch (op) { case "+": return 1; case "*": return 2; default: return 0; } } private void PushOperator(Stack<BTree> nodeStack, string op) { BTree right = nodeStack.Pop(); BTree left = nodeStack.Pop(); nodeStack.Push(new BTree(op) { Left = left, Right = right }); } } class TreeNodePosition { public BTree Node; public int X; public int Y; public TreeNodePosition Left, Right; } }
本文出自勇哥的网站《少有人走的路》wwww.skcircle.com,转载请注明出处!讨论可扫码加群:

本帖最后由 勇哥,很想停止 于 2025-06-25 22:56:12 编辑 
