关于编译原理:终于实现了一门属于自己的编程语言

48次阅读

共计 4853 个字符,预计需要花费 13 分钟才能阅读完成。

前言

都说程序员的三大浪漫是:操作系统、编译原理、图形学;最初的图形学的确是特定的业余畛域,咱们简直接触不到,所以对我来说换成网络更适合一些,最初再加上一个数据库。

这四项技术如果都能把握的话那岂不是在 IT 行业横着走了,加上这几年互联网行业越来越不景气,越底层的技术就越不可能被代替;所以为了给本人的 30+ 危机留点前途,从往年上半年开始我就逐步开始从头学习编译原理。

功夫不负有心人,通过近一个月的挑灯夜战,每晚都在老婆的督促下才劳动,克服了中途好几次想放弃的激动,终于当初实现了 GScript 一个预览版。

预览版的意思是语法结构与整体设计根本实现,后续更新也不太会改变这部分内容、但还短少一些易用性能。

个性

首先来看看保留环节,GScript 是如何编写 hello world 的。

hello_world.gs:

println("hello world");
❯ gscript hello_world.gs
hello world

废话说完了接下来重点聊聊 GScript 所反对的个性了。

后文会重点阐明每一个个性。

例子

除了方才提到的 hello world,再来看一个也是示例代码常常演示的 打印斐波那契数列

void fib(){
    int a = 0;
    int b = 1;
    int fibonacci(){
        int c = a;
        a = b;
        b = a+c;
        return c;
    }
    return fibonacci;
}
func int() f = fib();
for (int i = 0; i < 5; i++){println(f());
}

输入后果如下:

0
1
1
2
3

整体写法与 Go 官网举荐的相似:https://go.dev/play/p/NeGuDahW2yP

// fib returns a function that returns
// successive Fibonacci numbers.
func fib() func() int {
    a, b := 0, 1
    return func() int {
        a, b = b, a+b
        return a
    }
}
func main() {f := fib()
    // Function calls are evaluated left-to-right.
    fmt.Println(f(), f(), f(), f(), f())
}

都是通过闭包变量实现的,同时也展现了 GScript 对闭包、函数的应用,后文具体介绍闭包的用法。

语法

GScript 的语法与常见的 Java/Go 相似,所以上手非常简单。

根本类型

先来看看根本类型,目前反对 int/string/float/bool 四种根本类型以及 nil 非凡类型。

变量申明语法和 Java 相似:

int a=10;
string b,c;
float e = 10.1;
bool f = false;

集体感觉将类型放在后面,代码浏览起来会更清晰一些,当然这也是集体爱好。

数组

// 申明并初始化
int[] a={1,2,3};
println(a);

// 申明一个空数组并指定大小
int[] table = [4]{};

println();
// 向数组 append 数据
a = append(a,4);
println(a);
for(int i=0;i<len(a);i++){println(a[i]);
}

// 通过下标获取数组数据
int b=a[2];
println(b);

其实严格来讲这并不算是数组,因为它的底层是用 Go 切片实现的,所以能够动静扩容。

以这段代码为例:

int[] a=[2]{};
println("数组大小:"+len(a));
a = append(a,1);
println("数组大小:"+len(a));
println(a);
a[0]=100;
println(a);

输入:

数组大小:2
数组大小:3
[<nil> <nil> 1]
[100 <nil> 1]

Class

类的反对十分重要,是实现面向对象的根底,目前还未齐全实现面向对象,只实现了数据与函数的封装。

class ListNode{
    int value;
    ListNode next;
    ListNode(int v, ListNode n){
        value =v;
        next = n;
    }
}

// 调用构造函数时不须要应用 new 关键字。ListNode l1 = ListNode(1, nil);

// 应用 . 调用对象属性或函数。println(l1.value);

缺省状况下 class 具备无参构造函数:

class Person{
    int age=10;
    string name="abc";
    int getAge(){return 100+age;}
}

// 无参构造函数
Person xx= Person();
println(xx.age);
assertEqual(xx.age, 10);
println(xx.getAge());
assertEqual(xx.getAge(), 110);

得益于 class 的实现,联合方才的数组也能够定义出自定义类型的数组:

// 大小为 16 的 Person 数组
Person[] personList = [16]{};

函数

函数其实分为两类:

  • 一般的全局函数。
  • 类的函数。

实质上没有任何区别,只是所属范畴不同而已。

// 判断链表是否有环
bool hasCycle(ListNode head){if (head == nil){return false;}
    if (head.next == nil){return false;}

    ListNode fast = head.next;
    ListNode slow = head;
    bool ret = false;
    for (fast.next != nil){if (fast.next == nil){return false;}
        if (fast.next.next == nil){return false;}
        if (slow.next == nil){return false;}
        if (fast == slow){
            ret = true;
            return true;
        }

        fast = fast.next.next;
        slow = slow.next;
    }
    return ret;
}

ListNode l1 = ListNode(1, nil);
bool b1 =hasCycle(l1);
println(b1);
assertEqual(b1, false);

ListNode l4 = ListNode(4, nil);
ListNode l3 = ListNode(3, l4);
ListNode l2 = ListNode(2, l3);
bool b2 = hasCycle(l2);
println(b2);
assertEqual(b2, false);

l4.next = l2;
bool b3 = hasCycle(l2);
println(b3);
assertEqual(b3, true);

这里演示了链表是否有环的一个函数,只有有其余语言的应用根底,置信浏览起来没有任何问题。

add(int a){}

当函数没有返回值时,能够申明为 void 或间接疏忽返回类型。

闭包

闭包我认为是十分有意思的一个个性,能够实现很灵便的设计,也是函数式编程的根底。

所以在 GScript 中函数是作为一等公民存在;因而 GScript 也反对函数类型的变量。

函数变量申明语法如下:func typeTypeOrVoid '(' typeList? ')'

// 内部变量,全局共享。int varExternal =10;
func int(int) f1(){
    // 闭包变量对每个闭包独自可见
    int varInner = 20;
    int innerFun(int a){println(a);
        int c=100;
        varExternal++;
        varInner++;
        return varInner;
    }
    // 返回函数
    return innerFun;
}

// f2 作为一个函数类型,接管的是一个返回值和参数都是 int 的函数。func int(int) f2 = f1();
for(int i=0;i<2;i++){println("varInner=" + f2(i) + ", varExternal=" + varExternal);
}
println("=======");
func int(int) f3 = f1();
for(int i=0;i<2;i++){println("varInner=" + f3(i) + ", varExternal=" + varExternal);
}

最终输入如下:

0
varInner=21, varExternal=11
1
varInner=22, varExternal=12
=======
0
varInner=21, varExternal=13
1
varInner=22, varExternal=14
func int(int) f2 = f1();

以这段代码为例:f2 是一个返回值,入参都为 int 的函数类型;所以后续能够间接当做函数调用 f2(i).

例子中将闭包别离赋值给 f2 和 f3 变量,这两个变量中的闭包数据也是相互隔离、互不影响的,所有基于这个个性甚至还是实现面向对象。

对于闭包的实现,后续会独自更新一篇。

更多样例请参考:https://github.com/crossoverJie/gscript/tree/main/example

规范库

规范库源码:https://github.com/crossoverJie/gscript/tree/main/internal

目前实现的规范库并不多,这齐全是一个体力活;基于现有的语法和根底数据类型,简直能够实现大部分的数据结构了,所以感兴趣的敌人也欢送来奉献规范库代码;比方 StackSet 之类的数据结构。

MapString

以这个 MapString 为例:键值对都为 stringHashMap

int count =100;
MapString m1 = MapString();
for (int i=0;i<count;i++){
    string key = i+"";
    string value = key;
    m1.put(key,value);
}
println(m1.getSize());
assertEqual(m1.getSize(),count);

for (int i=0;i<count;i++){
    string key = i+"";
    string value = m1.get(key);
    println("key="+key+ ":"+ value);
    assertEqual(key,value);
}

应用起来和 JavaHashMap 相似,当然他的实现源码也是参考的 jdk1.7 的 HashMap

因为目前并有一个相似于 Java 的 object 或者是 go 中的 interface{}, 所以如果须要寄存 int,那还得实现一个 MapInt,不过这个通用类型很快会实现。

内置函数

int[] a={1,2,3};
// len 返回数组大小
println(len(a));

// 向数组追加数据
a = append(a,4);
println(a);
// output: [1,2,3,4]

// 断言函数,不相等时会抛出运行时异样,并中断程序。assertEqual(len(a),4);

// 返回 hashcode
int hashcode = hash(key);

也内置了一些根本函数,当然也这不是由 GScript 源码实现的,而是编译器实现的,所以新增起来要略微麻烦一些;后续会逐步完善,比方和 IO 相干的内置函数。

总结

现阶段的 GScript 还有许多性能没有欠缺,比方 JSON、网络库、更欠缺的语法查看、编译报错信息等;当初拿来刷刷 LeetCode 还是没有问题的。

从这 65 个 todo 就能看出还有很长的路要走,我对它的终极目标就是能够编写一个网站那就算是一个成熟的语言了。

目前还有一个问题是没有集成开发环境,当初的开发体验和白板上写代码相差无异,所以后续有工夫的话尝试写一个 VS Code 的插件,至多能有语法高亮与提醒。

最初对 GScript 或者是编译原理感兴趣的小伙伴能够加我微信一起交换。

我的项目源码:https://github.com/crossoverJie/gscript

下载地址:https://github.com/crossoverJie/gscript/releases/tag/v0.0.6

正文完
 0