摘要

程序开发行业中有很多种编程语言,每个程序员大概也都会一两种,可你有没有想过自己DIY一种语言呢,本文就带你用.net DIY一种新语言--WawaSharp,我们将定义语法,实现词法分析,建立语法树,代码生成几个过程。

引言

不要为摘要里的那些名词吓住了,什么词法分析,语法树之类的,其实要实现一个简单的语言并不复杂,就是做一些字符串的操作,以及运用几个IL指令。以前我也以为很复杂,很神秘,直到我发现了如下这篇帖子
创建 .NET Framework 语言编译器

http://msdn.microsoft.com/zh-cn/magazine/cc136756.aspx

本文也是根据这篇帖子来的,只是在其基础上支持了更多的语句和表达式,所以大家可以先看懂这篇文章,本文对这篇文章里讲到的东西也就不再详细重复。

我们要提高一个程序灵活性的时候常常把一些变量做成配置,这时候需求变了的话,修改一下配置就可以满足需求了,可有时候配置不足以满足需求的变化,所以一些更NB的程序,可以提供一个SDK及一套自定义语言让使用者去二次开发,今天我们发明的语言就可以去当作这种场景下的自定义语言。另外一个目的就是和大家一起了解一下一个语言背后的故事,我们写的文本代码是如何变成可执行的程序的。

语法定义

摘要里也讲了,发明语言的第一步是定义语法,定义语法一般用BNF,我也不懂这是嘛,比猫画虎做了一个,如下,大家也不用去折腾它到底是啥意思,就扫一眼,凭直觉,能看懂多少算多少。


<stmt> := var <ident> = <expr>
    | <ident> = <expr>
    | for <ident> = <expr> to <expr> do <stmt> end
    | foreach <ident> in <expr> do <stmt> end
    | if <expr> then <stmt> end
    | read_int <ident>
    | print <expr>
    | <stmt> ; <stmt>
    | append <expr> <expr>

<expr> := <string>
    | <int>
    | <arith_expr>
    | <ident>
    | match <expr> <expr>
    | newsb
    | len <expr>

<bin_expr> := <expr> <bin_op> <expr>
<bin_op> := + | - | * | / | == | eq

<ident> := <char> <ident_rest>*
<ident_rest> := <char> | <digit>

<int> := <digit>+
<digit> := 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9

<string> := " <string_elem>* "
<string_elem> := <any char other than ">

语法有了,我们看下我们要实现的语言大概是什么样子,如下

var input = ""11|222|33|44|55"";
var arr = match input ""/d+|"";
var sb = newsb;
foreach item in arr do
    print item;
    var l = len item;
    if l eq 2 then
        var arr_ = match item ""/d"";
        foreach item_ in arr_ do
            append sb ""/r/n"";
            append sb item_;
        end;
    end;
end;
print sb;

综合语法定义和例子,我们可以看到,我们定义了string,bool,int,enumerable几种数据类型,var,foreach,if,print,append等几种语句,还有赋值,match,len,Equals,整形常量,字符串常量等几种表达式。

词法分析

所谓词法分析,就是把文本拆成一个一个的块儿(token),用过lucene的应该比较熟悉,类似lucene分词的过程,比如去除停止词,就是把对程序无关的,比如空白字符,回车字符等删除掉,然后做一个List,顺序把拆除来的有效字符块儿放进去,比如以上的例子,就会拆出var,input,=,"11|222|33|44|55",;,var,arr,=...等。我们的变量名只支持字母和下划线,不支持数字。

把需求说清楚了,实现这个应该很简单吧,就写一个while循环,一个一个的读取字符,每满足一个token规则就放到list里面一个就行了,伪码如下


while (input.Peek() != -1)
{
    char ch = (char)input.Peek();
    if (char.IsWhiteSpace(ch))
    {
        //忽略空白字符
        input.Read();
    }
    else if (char.IsLetter(ch) || ch == '_')
    {
        //取出标识符,以字母和下划线组成
    }
    else if (ch == '"')
    {
        //取出字符串常量
    }
    else if (char.IsDigit(ch))
    {
        //取出数字常量
    }
    else switch (ch)
    {
        //取出单字符操作符,如+,-,*,/等
    }
}

到此,我们有了一个IList<object>,里面顺序放着我们分析出来的文本块儿。

语法分析

语法分析是把上一步生成的文本块序列折腾成一棵树,叫语法树,我们得先定义各种抽象数据结构,如顺序,分支,循环语句,赋值,比较等表达式等,举几个例子如下。

public abstract class Stmt{}
public abstract class Expr {}
public class DeclareVar : Stmt
{
    public Expr Expr;
    public string Ident;
}
public class Assign : Stmt
{
    public Expr Expr;
    public string Ident;
}
public class Sequence : Stmt
{
    public Stmt First;
    public Stmt Second;
}
public class Foreach : Stmt
{
    public Stmt Body;
    public string Ident;
    public Expr IEnumerable;
}
public class If : Stmt {
    public Stmt Body;
    public Expr Condition;
}
public class StrLen : Expr
{
    public Expr Input;
}        
public class Match : Expr
{
    public Expr Input;
    public Expr Pattern;
}

意思就是我们要把token序列转换成一个由上述这些对象组成的一颗树,差不多是二叉树,这一步叫语法分析,据说要多复杂有多复杂,但咱们这里还算比较简单,也是一个while循环,比如碰到if语句,就根据语法定义if <expr> then <stmt> end,下一个token应该是个表达式,表示if语句的条件,那么就继续出一个表达式作为if语句的Condition树形,再往后要有一个then,再往后是一个语句,解析出一条完整语句,作为if的Body树形,完了最后以一个end结尾,大概伪码如下吧。

private Stmt ParseStmt() {
    if (tokens[index].Equals("print")) {
        index++;
        var print = new Print();
        print.Expr = ParseExpr();
        result = print;
    }
    else if (tokens[index].Equals("append"))
    {
            index++;
            var append = new Append();
            append.Buider = ParseExpr();
            append.ToAppend = ParseExpr();
    }
    else if (tokens[index].Equals("var")) {}
    else if (tokens[index].Equals("foreach")) {}
    else if (tokens[index].Equals("if")) {}
    else{ thr ex;}
    if (index < tokens.Count && tokens[index] == Scanner.Semi) {
        index++;    
        if (index < tokens.Count &&!tokens[index].Equals("end")) {
            var sequence = new Sequence();
            sequence.First = result;
            sequence.Second = ParseStmt();
            result = sequence;
        }
    }
}    
private Expr ParseExpr() {
    if (tokens[index] is StringBuilder) {//StringLiteral}
    else if (tokens[index] is int) {//IntLiteral}
    else if (tokens[index] is string) {
        string value = (string)tokens[index];
    if (value.Equals("match")) {//Match}
    else if (value.Equals("newsb")) {//Builder}
    else if (value.Equals("len")) {//Strlen}
    }
}

最终,我们会形成一个语法树,比如我们示例代码的分析结果如下

[Sequence]
  |First:
  |  |[DeclareVar]
  |  |  |Ident:input
  |  |  |Expr:
  |  |  |  |<StringLiteral>:"11|222|33|44|55"
  |Second:
  |  |[Sequence]
  |  |  |First:
  |  |  |  |[DeclareVar]
  |  |  |  |  |Ident:arr
  |  |  |  |  |Expr:
  |  |  |  |  |  |<Match>:
  |  |  |  |  |  |  |Input:
  |  |  |  |  |  |  |  |<Variable>:input
  |  |  |  |  |  |  |Pattern:
  |  |  |  |  |  |  |  |<StringLiteral>:"/d+|"
  |  |  |Second:
  |  |  |  |[Sequence]
  |  |  |  |  |First:
  |  |  |  |  |  |[DeclareVar]
  |  |  |  |  |  |  |Ident:sb
  |  |  |  |  |  |  |Expr:
  |  |  |  |  |  |  |  |[Builder]
  |  |  |  |  |Second:
  |  |  |  |  |  |[Sequence]
  |  |  |  |  |  |  |First:
  |  |  |  |  |  |  |  |<Foreach>:item
  |  |  |  |  |  |  |  |  |IEnumerable:
  |  |  |  |  |  |  |  |  |  |<Variable>:arr
  |  |  |  |  |  |  |  |  |Body:
  |  |  |  |  |  |  |  |  |  |[Sequence]
  |  |  |  |  |  |  |  |  |  |  |First:
  |  |  |  |  |  |  |  |  |  |  |  |[Print]
  |  |  |  |  |  |  |  |  |  |  |  |  |Expr:
  |  |  |  |  |  |  |  |  |  |  |  |  |  |<Variable>:item
  |  |  |  |  |  |  |  |  |  |  |Second:
  |  |  |  |  |  |  |  |  |  |  |  |[Sequence]
  |  |  |  |  |  |  |  |  |  |  |  |  |First:
  |  |  |  |  |  |  |  |  |  |  |  |  |  |[DeclareVar]
  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |Ident:l
  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |Expr:
  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |<StrLen>:
  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |Input:
  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |<Variable>:item
  |  |  |  |  |  |  |  |  |  |  |  |  |Second:
  |  |  |  |  |  |  |  |  |  |  |  |  |  |<If>:
  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |Condition:
  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |<BinExpr>:Eq
  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |left:
  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |<Variable>:l
  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |Right:
  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |<IntLiteral>:2
  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |Body:
  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |[Sequence]
  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |First:
  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |[DeclareVar]
  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |Ident:arr_
  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |Expr:
  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |<Match>:
  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |Input:
  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |<Variable>:item
  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |Pattern:
  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |<StringLiteral>:"/d"
  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |Second:
  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |<Foreach>:item_
  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |IEnumerable:
  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |<Variable>:arr_
  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |Body:
  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |[Sequence]
  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |First:
  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |[Append]:
  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |Buider:
  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |<Variable>:sb
  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |ToAppend:
  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |<StringLiteral>:""
  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |Second:
  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |[Append]:
  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |Buider:
  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |<Variable>:sb
  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |ToAppend:
  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |<Variable>:item_
  |  |  |  |  |  |  |Second:
  |  |  |  |  |  |  |  |[Print]
  |  |  |  |  |  |  |  |  |Expr:
  |  |  |  |  |  |  |  |  |  |<Variable>:sb

我在Stmt和Expr类各定义了个ToString方法来用文本显示这棵树,最终结果类似上面这样的显示,虽然不是很美,但意思能表达出来,我们的分析结果确实是棵树,CodeDom有树,表达式树有树,咱们的WawaSharp也得有树。

看到这里,大家可以稍微休息休息,目前为止,我们还没碰到什么新鲜的,就是一些while语句和一些字符串拆分逻辑,下一篇会讲到代码生成,就是根据这棵树生成可执行的IL代码,有趣的是生成IL代码后还能用Reflector反编译成c#和vb代码,呵呵。

蛙蛙推荐:蛙蛙教你发明一种新语言之一--词法分析和语法分析相关推荐

  1. 蛙蛙推荐:蛙蛙教你发明一种新语言之二--代码生成

    摘要 上一篇里我们构建了语法树,但他并不能执行,还要把它转换成可执行的代码.语法树是抽象的,可以把它转换到各种平台的执行文件,但我们现在只关注它是如何生成一个CLR的可执行文件. IL指令介绍 书接上 ...

  2. 蛙蛙推荐:蛙蛙教你文本聚类 - 蛙蛙王子 - 博客园

    蛙蛙推荐:蛙蛙教你文本聚类 - 蛙蛙王子 - 博客园 蛙蛙推荐:蛙蛙教你文本聚类 - 蛙蛙王子 - 博客园 蛙蛙推荐:蛙蛙教你文本聚类 摘要:文本聚类是搜索引擎和语义web的基本技术,这次本蛙和大家一 ...

  3. 蛙蛙推荐:蛙蛙教你文本聚类

    蛙蛙推荐:蛙蛙教你文本聚类 摘要:文本聚类是搜索引擎和语义web的基本技术,这次本蛙和大家一起学习一下简单的文本聚类算法,可能不能直接用于实际应用中,但对于想学搜索技术的初学者还是有一定入门作用的.这 ...

  4. 蛙蛙推荐:蛙蛙牌网页捕捉器

    蛙蛙推荐:蛙蛙牌网页捕捉器 摘要:你有没有看到一篇好文章想保存到本地,有没有想过只保存网页选中的部分而不要那些不必要的导航和广告,本贴告诉你达到这个目的的思路及主要代码. 思路:首先我们要获取到所有I ...

  5. 蛙蛙推荐:蛙蛙浏览器

    蛙蛙推荐:蛙蛙浏览器 摘要:google推出了自己的网页浏览器,现在web浏览器的竞争更激烈了,各有各的用户群.其实有另一个领域没有多少竞争,那就是应用程序浏览器,今天给大家演示的蛙蛙浏览器,不仅可以 ...

  6. 蛙蛙推荐:蛙蛙牌软件注册码算法

    蛙蛙推荐:蛙蛙牌软件注册码算法 摘要:辛辛苦苦写个共享软件,又怕被人破解,所以就会想到用注册码的方式来激活软件.本蛙给大家一个简单的思路来实现软件注册码算法,当然.net做的东西很容易被人破解,反编译 ...

  7. python编程例子 输入 输出-推荐 :手把手教你用Python创建简单的神经网络(附代码)...

    原标题:推荐 :手把手教你用Python创建简单的神经网络(附代码) 作者:Michael J.Garbade:翻译:陈之炎:校对:丁楠雅 本文共2000字,9分钟. 本文将为你演示如何创建一个神经网 ...

  8. 【报告分享】女性自我保护手册,教你应对10种常见危险处境.pdf(附189页pdf下载链接)...

    前两周PUA沸沸扬扬,今天给广大女粉丝分享一篇手册<女性自我保护手册,教你应对10种常见危险处境 .pdf>,该手册由北京大学心理资讯中心和壹心理联合出品,我简单看了一下,手册内容非常丰富 ...

  9. EDM模板设计:教您设计三种独特的邮件营销模板

    教您设计三种独特的邮件营销模板 邮件营销,模板 众所周知,好的邮件营销必须要有好的模板设计,这也是EDM设计研究中非常重要的一个环节.下面博主教大家设计三种独特的邮件营销模板,供大家参考和学习. 一. ...

最新文章

  1. 关于敏捷开发的最佳实践和工具
  2. 阿里年会的马老师说:认真生活、快乐工作、保持理想
  3. 【Java基础】面向对象特性
  4. 深度学习(三十二)——AlphaGo, AlphaStar
  5. USACO2.1【bfs,排序,贪心,dfs,位运算】
  6. Canvas之进度条的制作(矩形,圆环)
  7. 对几个重要问题的阐述
  8. LeetCode —— 71.简化路径(Python3)
  9. HDU 1027 全排列
  10. 解决Linux系统下磁盘IO紧张的一种方法
  11. 關于dotNet開發中的框架思考
  12. CRUD了3 年从8K涨到30K,谁知道这4个月我到底经历了什么?
  13. 揭秘山寨手机“四寨主”:都高仿苹果iPhone
  14. JavaScript --------WebS APIs学习之网页特效(offset系列)
  15. SpringBoot项目遇到AopAutoConfiguration matched: - @ConditionalOnProperty (spring.aop.auto=true)错误
  16. Vue中使用 Aplayer 和 Metingjs 添加音乐插件
  17. python花瓣长度和花瓣宽度散点图鸢尾花_matplotlib可视化操作及案例分析
  18. 【GRNN情绪识别】基于GRNN神经网络的情绪识别算法matlab仿真
  19. Scratch快速入门(一)
  20. 饥荒联机版Mod开发——衣服(十一)

热门文章

  1. C 语言实现 - 判断元音/辅音
  2. 用java判断三角形类型_判断三角形类型
  3. windows XP 驱动开发环境搭建
  4. 数据结构-栈容器的实现
  5. 微信公众号扫码授权登录思路
  6. matlab正序零序负序,正序负序零序的理解
  7. 使用HM NIS Edit制作软件安装包
  8. 基于Simulink的高速跳频通信系统抗干扰性能
  9. 分享一个便宜又好用的代理ip
  10. 【数字图像处理之(一)】数字图像处理与相关领域概述