C#和WPF实现图形化编程
图形化编程在很多领域都应用很多,比如儿童编程、硬件控制,目前最火的两个引擎肯定应该是scratch和google blocky,很多图形化编程软件都是基于这两个引擎,比如国内编程猫使用了scratch,makecode使用了google blocky。
使用其他引擎必然会收到很大的限制,不如要实现比如局部变量等基本功能时候,使用scratch就没法实现了,而且scratch和blocky都是基于javascript,如果要控制某些硬件,就要做很多javascript和其他硬件的通信模块,不是很方便,所以我们也许要自己写一个图形化编程工具。我工作一直用图形化工具控制硬件,但是老外的软件做的很一般,有天突然想是否自己也能写一个图形化编程工具,就花了点时间,用C#和WPF写了一个图形化工具,代码已经放在github下开源
代码地址 https://github/weihuajiang/WPF-Blocky
也在代码中实现了一个代码编辑器,和scratch一样的编辑器
程序语法树AST的实现
图形化程序也是程序,要显示和运行,我们必须要知道程序语法树(abstract syntax tree)的概念。语法树可以分为表达式Expression、语句Statement和声明Declaration。比如简单的一个语句a+b,就是一个BinaryExpression,而a+b;虽然多了分号,不过就编程了一个Statement语句,这个是个ExpressionStatement,其中的表达式就是BinaryExpression。更多AST的信
息大家可以看esprima解释的很详细。在我们程序中,我们定义了AST基类Node和三个基本的语法树结构Expression、Statement和Decaration(主要就是函数定义),与之对应基本控件也就只有三个控件,Expression控件、Statement控件和Function控件。WPF的数据绑定,让控件编写简化了很多。
程序的运行
程序的运行,简单来说就是各个语法树节点的运行,比如a+5;可以转换成以下结构:
"type": "ExpressionStatement",
"expression": {
"type": "BinaryExpression",
"operator": "+",
"left": {
"type": "Identifier",
"name": "a"
},
"right": {
"type": "Literal",
"value": 5,
"raw": "5"
}
}
执行顺序是ExpressionStatement->BinaryExpression->left, right,返回left+right,left是Identifier是变量,需要从运行环境获得变量的值,right是Literal是整数5,这样就能获得运行的结果。
程序跳转的实现
break、continue和return,还有异常都会导致程序运行顺序发生跳转,我们通过程序返回值类型来实现程序的跳转。返回值总共有五种类型,分别对应五种可能情况,如果执行过程中出现异常,则应返回Exception,如果正确执行返回Value类型,循环会处理continue和break,函数处理return,如果执行过程中返回非Value,则应该把改返回类型返回,这样就解决了程序跳转的问题。
public enum CompletionType
{
Value,
Continue,
Break,
Return,
Exception
}
public class Completion
{
public static Completion Void = new Completion();
public Node Location { get; internal set; } = null;
public CompletionType Type { get; internal set; } = CompletionType.Value;
public object ReturnValue { get; internal set; } = null;
public bool IsValue
{
get
{
return Type == CompletionType.Value;
}
}
public bool IsException
{
get
{
return Type == CompletionType.Exception;
}
}
}
运行环境的实现
我们使用Dictionary来保存运行运行环境的变量值,当前变量就保存在当前的Dictionary中,运行到一个位置,比如需要一个新的运行环境时,就生成一个新的环境,这样在这个环境中定义的变量在parent环境就无法访问,也就实现了局部变量的操作,同时当前环境可以访问前边所有parent环境的数据,也就是访问其他作用域的变量。
public class ExecutionEnvironment
{
ExecutionEnvironment _parent=null;
Dictionary<string, object> currentVariables = new Dictionary<string, object>();
public ExecutionEnvironment()
{
_parent = null;
}
public ExecutionEnvironment(ExecutionEnvironment parent)
{
_parent = parent;
}
public bool HasValue(string variable)
{
if (currentVariables.ContainsKey(variable))
return true;
if (_parent != null)
return _parent.HasValue(variable);
return false;
}
public void RegisterValue(string variable, object value)
{
if (currentVariables.ContainsKey(variable))
throw new Exception(“Variable was defined already");
currentVariables.Add(variable, value);
}
public object GetValue(string variable)
{
if (currentVariables.ContainsKey(variable))
{
return currentVariables[variable];
}
if (_parent != null)
return _parent.GetValue(variable);
throw new KeyNotFoundException();
}
public void SetValue(string variable, object value)
{
if (currentVariables.ContainsKey(variable))
{
currentVariables[variable] = value;
return;
}
else if (_parent != null)
{
_parent.SetValue(variable, value);
return;
}
else
throw new KeyNotFoundException();
}
}
具体实现的例子
我们下边以if语句为例,看看具体如何实现语句的运行,if语句包含三部分,条件判断表达式Test,true时候运行的Consequent语句,false时候运行的Alternate语句,运行时候,我们先执行Test.ExecuteImpl语句,如果为true,则运行Consequent,false时候运行Alternate,不过在Consequent和Alternate运行时候,都产生了一个新的变量作用域,我们需要新建一个运行环境。
public class IfStatement : Statement
{
public IfStatement()
{
Consequent = new BlockStatement();
}
public Expression Test { get; set; }
public BlockStatement Consequent {get; set;}
public BlockStatement Alternate {get; set;}
protected override Completion ExecuteImpl(ExecutionEnvironment enviroment)
{
if (Test == null)
return Completion.Void;
var t = Test.Execute(enviroment);
if (!t.Type.IsValue)
return t;
if (t.ReturnValue is bool)
{
if ((bool)t.ReturnValue)
{
if (Consequent == null)
return Completion.Void;
ExecutionEnvironment current = new ExecutionEnvironment(enviroment);
return Consequent.Execute(current);
}
else
{
if (Alternate == null)
return Completion.Void;
ExecutionEnvironment current = new ExecutionEnvironment(enviroment);
return Alternate.Execute(current);
}
}
else
return Completion.Exception("Not a boolean value", Test);
}
}
结语
在githup上开源了编辑器的代码,大家可以下载看看,同时这个编辑器放在了微软商店里,链接是Visual Code Editor,如果想用微软商店直接安装,可以点击下边连接,
ms-windows-store://pdp/?productid=9NG2QVSXT34H
里边除了基本的语法功能,增加了更多类库和多语言等功能,比如python turtle一样的画板、翻译、语音、数学、数据结构等
更多推荐
C#和WPF实现图形化编程
发布评论