Handwritten Programming Language - How to write a standard library for GScript

new version update

Recently GScriptupdated v0.0.11version, focusing on updating:

  • DockerOperating environment
  • Added byte primitive type
  • Added some strings to the standard libraryStrings/StringBuilder
  • Array slice syntax:int[] b = a[1: len(a)];

Please see below for specific updates.

foreword

posted online some time GScriptago playground,playground-min.gif

This is a website that can run GScriptscripts . Its essential principle is to receive the user's input source code to run a service on the server; this is simply a backdoor open XSSattack. In order to keep the server, I set the user rights APIof backend service. , which can avoid executing some malicious requests.

But it is unavoidable that some users perform some time-consuming operations, such as an infinite loop, demoor printing the Yanghui triangle in the .

This is essentially a recursive function, which can be very time-consuming and CPU-intensive when the number of printed triangle layers is too high.

A few times when I went to check the server, I found several processes with too high CPU. Basically, it is such a time-consuming operation, which will inevitably affect the performance of the server.

Use Docker

In order to solve this kind of problem, it is natural to think that it can be used Docker. All resources are isolated from the host, and no matter how much you mess around, it will not affect the host.

Just do it, and finally modify the place where the API executes the script:

    string fileName = d.unix("Asia/Shanghai") + "temp.gs" ;
    s.writeFile(fileName, body, 438);
    string pwd = s.getwd();
    // string res = s.command("gscript", fileName);
    string res = s.command("docker","run","--rm","-v", pwd+":/usr/src/gscript","-w","/usr/src/gscript", "crossoverjie/gscript","gscript", fileName);
    s.remove(fileName);
    r.body = res;
    r.ast = dumpAST(body);
    r.symbol=dumpSymbol(body);
    ctx.JSON(200, r);
复制代码

The main modification is to change the directly executed GScriptcommand to call dockerexecution.

But in fact, there is still room for improvement. After adding a new coroutine, you can monitor the running time, and automatically kill the process after the timeout.

I also Dockeruploaded it, and now everyone just needs to run it when DockerHubthey want to experience GScriptit .REPLDocker

docker pull crossoverjie/gscript
docker run --rm -it  crossoverjie/gscript:latest gscript
复制代码

当然也可以执行用 Docker 执行 GScript 脚本:

docker run --rm -v $PWD:/usr/src/gscript -w /usr/src/gscript crossoverjie/gscript gscript {yourpath}/temp.gs
复制代码

编写 GScript 标准库

接下来重点聊聊 GScript 标准库的事情,其实编写标准库是一个费时费力的事情。 现在编译器已经提供了一些可用的内置函数,借由这些内置函数写一些常见的工具类是完全没有问题的。

对写 GScript 标准库感谢的朋友可以当做一个参考,这里我打了一个样,先看下运行效果:

// 字符串工具类
StringBuilder b = StringBuilder();
b.writeString("10");
b.writeString("20");
int l = b.writeString("30");
string s = b.String();
printf("s:%s, len=%d ",s,l);
assertEqual(s,"102030");
byte[] b2 = toByteArray("40");
b.WriteBytes(b2);
s = b.String();
assertEqual(s,"10203040");
println(s);

// Strings 工具类
Strings s = Strings();
string[] elems = {"name=xxx","age=xx"};
string ret = s.join(elems, "&");
println(ret);
assertEqual(ret, "name=xxx&age=xx");

bool b = s.hasPrefix("http://www.xx.com", "http");
println(b);
assertEqual(b,true);
b = s.hasPrefix("http://www.xx.com", "https");
println(b);
assertEqual(b,false);
复制代码

其中的实现源码基本上是借鉴了 Go 的标准库,先来看看 StringBuilder 的源码:

class StringBuilder{
    byte[] buf = [0]{};

    // append contents to buf, it returns the length of s
    int writeString(string s){
        byte[] temp = toByteArray(s);
        append(buf, temp);
        return len(temp);
    }
    
    // append b to buf, it returns the length of b.
    int WriteBytes(byte[] b){
        append(buf, b);
        return len(b);
    }

    // copies the buffer to a new.
    grow(int n){
        if (n > 0) {
            // when there is not enough space left.
            if (cap(buf) - len(buf) < n) {
                byte[] newBuf = [len(buf), 2*cap(buf)+n]{};
                copy(newBuf, buf);
                buf = newBuf;
            }
        }   
    }

    string String(){
        return toString(buf);
    }
}
复制代码

主要就是借助了原始的数组类型以及 toByteArray/toString 字节数组和字符串的转换函数实现的。

class Strings{
    // concatenates the elements of its first argument to create a single string. The separator
    // string sep is placed between elements in the resulting string.
    string join(string[] elems, string sep){
        if (len(elems) == 0) {
            return "";
        }
        if (len(elems) == 1) {
            return elems[0];
        }
        
        byte[] bs = toByteArray(sep);
        int n = len(bs) * (len(elems) -1);
        for (int i=0; i < len(elems); i++) {
            string s = elems[i];
            byte[] bs = toByteArray(s);
            n = n + len(bs);
        }
        
        StringBuilder sb = StringBuilder();
        sb.grow(n);
        string first = elems[0];
        sb.writeString(first);

        string[] remain = elems[1:len(elems)];
        for(int i=0; i < len(remain); i++){
            sb.writeString(sep);
            string r = remain[i];
            sb.writeString(r);
        }
        return sb.String();

    }
    
    // tests whether the string s begins with prefix.
    bool hasPrefix(string s, string prefix){
        byte[] bs = toByteArray(s);
        byte[] bp = toByteArray(prefix);    
        return len(bs) >= len(bp) && toString(bs[0:len(bp)]) == prefix;
    }
}
复制代码

Strings 工具类也是类似的,都是一些内置函数的组合运用;

在写标准库的过程中还会有额外收获,可以再次阅读一遍 Go 标准库的实现流程,换了一种语法实现出来,会加深对 Go 标准库的理解。

所以欢迎感兴趣的朋友向 GScript 贡献标准库,由于我个人精力有限,实现过程中可能会发现缺少某些内置函数或数据结构,这也没关系,反馈 issue 后我会尽快处理。

由于目前 GScript 还不支持包管理,所以新增的函数可以创建 Class 来实现,后续支持包或者是 namespace 之后直接将该 Class 迁移过去即可。


本文相关资源链接

Guess you like

Origin juejin.im/post/7155265817611862053