里程碑!用自己的编程语言实现了一个网站!
Posted 码农翻身
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了里程碑!用自己的编程语言实现了一个网站!相关的知识,希望对你有一定的参考价值。
前言
自制编程语言,网上的文章多如牛毛,但是真正能坚持着把一个语言给实现了,同时还能写一个应用程序员的就很少了。
大部分网上博文一般只写到词法分析、语法分析都比较少,后面就没有下文了。
这件事还是有不小门槛的,实现了编程语言,还得去实现相关类库,要不然它就是一个没多大用处的玩具。没有兴趣驱使着自己往前走,往往是坚持不下来的。
今天介绍一下@crossoverJie开发的编程语言:GScript。
他已经用自制的编程语言已经达到了一个小里程碑:开发了一个网站。
https://gscript.crossoverjie.top/index
要称为一个网站确实有点勉强,不过也是一个动态网页,因为返回的是 html
,所以在当前阶段只要不嫌麻烦其实也能写一个“合格”的网站,有点像以前我们学习 Java
时的 servlet
。
该页面的源码地址在这里:https://github.com/crossoverjie/gscript-homepage
其实总共也就40来行代码:
class GScript
string author;
string[] features;
string since;
GScript(string a, string[] f, string s)
author = a;
features = f;
since = s;
func (HttpContext) index(HttpContext ctx)
string[] features = "statically", "strongly";
GScript gs = GScript("crossoverJie",features, "2022");
string j = JSON(gs);
println(j);
string local = getCurrentTime("Asia/Shanghai","2006-01-02 15:04:05");
println("local=" + local);
string html = ^
<html>
<title>GScript</title>
<pre>
_ _
___ ___ ___ ___|_|___| |_
| . |_ -| _| _| | . | _|
|_ |___|___|_| |_| _|_|
|___| |_| v0.0.7
^+ j +^
</pre>
<h1>current ^+ local +^</h1>
<p><a href="https://github.com/crossoverjie/gscript-homepage">GScript-homepace source code</a></p>
</html>
^;
ctx.HTML(200, html);
httpHandle("GET", "/index", index);
string[] args = getOSArgs();
if (len(args) ==3)
httpRun(":" + args[2]);
else
httpRun(":8000");
全是利用 GScript
所提供的标准库实现的,后文会详细聊聊内置 HTTP 包。
特性
首先来看看保留环节, 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
目前实现的标准库并不多,这完全是一个体力活;基于现有的语法和基础数据类型,几乎可以实现大部分的数据结构了,所以感兴趣的朋友也欢迎来贡献标准库代码;比如 Stack
、Set
之类的数据结构。
MapString
以这个 MapString
为例:键值对都为 string
的 HashMap
。
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);
使用起来和 Java
的 HashMap
类似,当然他的实现源码也是参考的 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 相关的内置函数。
最新特性
any类型
首先是 any
通用类型,这个类似于 Java 中的 Object
和 Go 中的 interface
,极大的方便了我们编写一些标准库。
以之前内置的 hash 和 len 函数为例,需要对每种类型都实现一遍,非常麻烦而且毫无必要;现在只需要定义一次即可,代码量直接省几倍。
同理,之前实现的 Map 只支持存放 string 类型,现在便能存放任何类型的数据。
对 any 的实现过程感兴趣的朋友,今后可以单独分享一下。
运算符重载
写 go 或者是 Java 的朋友应该知道,这两门语言都无法对两个对象进行运算,编译器会直接报错。
但在一些特殊场景下还是蛮好用的,于是我参考了 C#
的语法在 GScript
中也实现了。
class Person
int age;
Person(int a)
age = a;
Person operator + (Person p1, Person p2)
Person pp = Person(p1.age+p2.age);
return pp;
Person operator - (Person p1, Person p2)
Person pp = Person(p1.age-p2.age);
return pp;
Person p1 = Person(10);
Person p2 = Person(20);
Person p3 = p1+p2;
println("p3.age="+p3.age);
assertEqual(p3.age, 30);
声明的函数名称必须为 operator
,之后跟上运算符便实现了重载。
支持的运算符有:+-*/ < >= <= > ==
。
JSON支持
当前版本中支持将对象、基本类型进行序列化,暂不支持反序列化为对象,但可以根据 JSON
字符串通过一定的语法查询数据。
内置了两个 JSON 相关函数:
// return JSON string
string JSON(any a)
// JSON query with path
any JSONGet(string json, string path)
class Person
int age;
string name;
float weight;
bool man;
Person(string n, int a, float w, bool m)
name = n;
age = a;
weight = w;
man =m;
Person p1 = Person("abc",10,99.99,true);
Person p2 = Person("a",11,999.99,false);
string json = JSON(p1);
println(json);
// output:"age":10,"man":true,"name":"abc","weight":99.99
以这段代码为例,调用 JSON
函数可以将对象序列化为 JSON
字符串。
class Person
int age;
string name;
float weight;
bool man;
Person(string n, int a, float w, bool m)
name = n;
age = a;
weight = w;
man =m;
Person p1 = Person("abc",10,99.99,true);
string json = JSON(p1);
println(json);
int age = JSONGet(json, "age");
println(age);
assertEqual(age,10);
使用 JSONGet
函数可以在一个 JSON 字符串中查询任意的数据,这个功能是通过适配 XJSON 实现的,所以 XJSON
支持的查询语法都能实现。
string j=^"age":10, "abc":"def":"def","list":[1,2,3]^;
String def = JSONGet(j, "abc.def");
println(def);
assertEqual(def,"def");
int l1 = JSONGet(j, "list[0]");
println(l1);
assertEqual(l1,1);
string str=^
"name": "bob",
"age": 20,
"skill":
"lang": [
"go":
"feature": [
"goroutine",
"channel",
"simple",
true
]
]
^;
String g = JSONGet(str, "skill.lang[0].go.feature[0]");
println(g);
assertEqual(g,"goroutine");
比如这样复杂的嵌套 JSON
,也能通过查询语法获取数据。
HTTP 包
HTTP 包是本次升级的重点,标准库中提供了以下函数和类:
// http lib
// Response json
FprintfJSON(int code, string path, string json)
// Resonse html
FprintfHTML(int code, string path, string html)
// path (relative paths may omit leading slash)
string QueryPath(string path)
string FormValue(string path, string key)
class HttpContext
string path;
JSON(int code, any v)
string json = JSON(v);
FprintfJSON(code, path, json);
HTML(int code, any v)
string html = v;
FprintfHTML(code, path, html);
string queryPath()
string p = QueryPath(path);
return p;
string formValue(string key)
string v = FormValue(path, key);
return v;
// Bind route
httpHandle(string method, string path, func (HttpContext) handle)
// println("path="+path);
HttpContext ctx = HttpContext();
handle(ctx);
// Run http server.
httpRun(string addr)
具体的使用流程:
通过定义一个函数变量实现自己的业务逻辑。
注册路由。
启动 HTTP 服务。
在自己的 handle
中可以通过 HttpContext
对象拿到请求上下文,可以获取请求参数以及响应数据。具体使用示例可以参考这份代码。
总结
现阶段的 GScript
还有许多功能没有完善,比如 更完善的语法检查、编译报错信息等;现在拿来刷刷 LeetCode
还是没有问题的。
刷题源码:https://github.com/crossoverJie/gscript/tree/main/example/leetcode
目前还有一个问题是没有集成开发环境,现在的开发体验和白板上写代码相差无异,所以后续有时间的话尝试写一个 VS Code 的插件,至少能有语法高亮与提示。
之后抽空再把 SQL
标准库实现了,这样就能愉快的 CURD
了。
最后对 GScript
或者是编译原理感兴趣的小伙伴可以加我微信一起交流。
项目源码:https://github.com/crossoverJie/gscript
下载地址:https://github.com/crossoverJie/gscript/releases/tag/v0.0.8
(完)
点击下方图片,查看更多精彩
以上是关于里程碑!用自己的编程语言实现了一个网站!的主要内容,如果未能解决你的问题,请参考以下文章