三菱fx3u plc的指令仿真的研究(二)


接上篇:

三菱fx3u plc的指令仿真的研究(一)


这一节写C#程序,它做下面几件事:

(1)指令转逻辑表达式

(2)逻辑表达式放入二叉树,以方便指令仿真时进行解析。


下面是生成的二叉树效果。

55a79de6989f975c67ea178b2909988a.png


源码如下:

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 编辑

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

会员中心
搜索
«    2025年6月    »
1
2345678
9101112131415
16171819202122
23242526272829
30
网站分类
标签列表
最新留言
    热门文章 | 热评文章 | 随机文章
文章归档
友情链接
  • 订阅本站的 RSS 2.0 新闻聚合
  • 扫描加本站机器视觉QQ群,验证答案为:halcon勇哥的机器视觉
  • 点击查阅微信群二维码
  • 扫描加勇哥的非标自动化群,验证答案:C#/C++/VB勇哥的非标自动化群
  • 扫描加站长微信:站长微信:abc496103864
  • 扫描加站长QQ:
  • 扫描赞赏本站:
  • 留言板:

Powered By Z-BlogPHP 1.7.2

Copyright Your skcircle.com Rights Reserved.

鄂ICP备18008319号


站长QQ:496103864 微信:abc496103864