接上篇:
这一节写C#程序,它做下面几件事:
(1)指令转逻辑表达式
(2)逻辑表达式放入二叉树,以方便指令仿真时进行解析。
其原理见论文:http://www.skcircle.com/?id=2663
下面是生成的二叉树效果。

源码如下:
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 编辑 

少有人走的路

















