├── .gitattributes ├── .gitignore ├── README.md ├── README_zh.md ├── build.sh ├── cmd ├── asm.go └── root.go ├── compiler ├── ast │ ├── exp.go │ ├── op.go │ ├── program.go │ └── stmt.go ├── codegen │ ├── const_table.go │ ├── context.go │ ├── exp_gen.go │ ├── func_table.go │ ├── name_table.go │ ├── stack.go │ ├── stack_frame.go │ ├── stmt_gen.go │ └── upvalue_table.go ├── complie.go ├── graph.go ├── lexer │ ├── lexer.go │ └── lexer_test.go ├── parser │ ├── exp.go │ ├── exp_test.go │ ├── parser.go │ ├── program.go │ ├── program_test.go │ ├── stmt.go │ └── stmt_test.go └── token │ └── token.go ├── doc ├── bnf.txt ├── builtin.md ├── std.md ├── std_Buffer.md ├── std_fs.md ├── std_os.md ├── syntax.md └── syntax_zh.md ├── go.mod ├── go.sum ├── main.go ├── proto ├── func_proto.go ├── instruction.go └── proto.go ├── std ├── Buffer.gs ├── embed.go ├── fs.gs └── os.gs ├── test.gs ├── util └── main.go └── vm ├── builtin.go ├── debug.go ├── evstack.go ├── instruction.go ├── proto_frame.go ├── stack_frame.go ├── symbol_tabel.go ├── types ├── array.go ├── buffer.go ├── closure.go ├── file.go └── object.go └── vm.go /.gitattributes: -------------------------------------------------------------------------------- 1 | *.gs linguist-language=txt -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | TODO 2 | bin 3 | tmp 4 | *.gsproto 5 | # *.gs -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## The Gscript Language 2 | 3 | English|[中文](https://github.com/gufeijun/gscript/blob/master/README_zh.md) 4 | 5 | **Gscript is a light, dynamic script language written in Go**. 6 | 7 | example: 8 | 9 | ```python 10 | import fs 11 | import os 12 | 13 | # define a function 14 | func writeSomeMsg(filepath) { 15 | # open, create and truncate text.txt in write-only mode 16 | let file = fs.open(filepath,"wct"); 17 | 18 | # write some message into file 19 | file.write("hello world!\n") 20 | file.write("gscript is a good language!") 21 | 22 | # close file 23 | file.close(); 24 | } 25 | 26 | let filepath = "./text.txt" 27 | 28 | try{ 29 | writeSomeMsg(filepath) 30 | 31 | let stat = fs.stat(filepath) 32 | print("size of text.txt is " + stat.size + "B"); 33 | 34 | # read all data from text.txt 35 | let data = fs.readFile(filepath); 36 | print("message of", filepath, "is:") 37 | print(data.toString()) 38 | 39 | # remove text.txt 40 | fs.remove(filepath) 41 | print("success!") 42 | } 43 | catch(e){ 44 | print("operation failed, error msg:",e) 45 | os.exit(0) 46 | } 47 | ``` 48 | 49 | ### Features 50 | 51 | + Function 52 | 53 | + Multiple return values 54 | 55 | + Closure 56 | + Recursive function call 57 | 58 | + All standard libraries are packaged into one executable, which means, to install or use gscript it's no need to configure some environment variables. 59 | 60 | + Object-oriented programming 61 | 62 | + Debug mode 63 | 64 | + Can generate human-readable assemble-like codes 65 | 66 | + Simple syntax, easy to learn, especially to coders with JavaScript or Python experiences. 67 | 68 | + Modular support 69 | 70 | + Exception support: try, catch and throw 71 | 72 | + Complied/executed as bytecode on stack-based VM 73 | 74 | ### Install 75 | 76 | Since compiler and VM are written in pure Go(no cgo) and does not use any platform-dependent interfaces, so Gscript is a cross-platform language. 77 | 78 | You can install Gscript in two ways: 79 | 80 | + Compile from source code. 81 | 82 | *note: we use the new feature "embed" of Go 1.16, so make sure your Go version is greater than or equal to 1.16*. 83 | 84 | ```shell 85 | git clone git@github.com:gufeijun/gscript.git 86 | cd gscript 87 | sh build.sh 88 | ``` 89 | 90 | then the compiler will generated to `bin/gsc`. 91 | 92 | + Download from [releases](https://github.com/gufeijun/gscript/releases). 93 | 94 | `gsc ` means `gscript compiler`. You can add the executable to `PATH` as you wish. 95 | 96 | Then all you need is just having fun. 97 | 98 | ### Quick Start 99 | 100 | open file `main.gs` and write codes below: 101 | 102 | ```python 103 | print("hello world"); 104 | ``` 105 | 106 | run the script: 107 | 108 | ```shell 109 | gsc run main.gs 110 | ``` 111 | 112 | you will get following output: 113 | 114 | ``` 115 | hello world 116 | ``` 117 | 118 | you can also do something more challenging: 119 | 120 | ```python 121 | print(fib(20)) 122 | 123 | func fib(x) { 124 | if (x == 0) return 0; 125 | if (x == 1) return 1; 126 | return fib(x-1) + fib(x-2) 127 | } 128 | ``` 129 | 130 | run the script, you will get 6765. 131 | 132 | ### Usage 133 | 134 | we demonstrated the command `run` of `gsc` above. You can use `gsc --help` for more details about how to use gsc. 135 | 136 | + use `gsc debug ` or `gsc debug `to enter debug mode, in which we can execute virtual instruction one by one and view stack and variable table changes in real time. 137 | 138 | + use `gsc build ` to generate bytecode, besides, you can use `-o` flag to specific name of output file. 139 | 140 | + use `gsc build -a ` or `gsc build -a ` to generate human-readable assemble-like codes. 141 | 142 | Take the `main.gs` in section `Quick Start` as an example, run the following command: 143 | 144 | ```shell 145 | gsc build -a main.gs -o output.gscasm 146 | ``` 147 | 148 | It will generate `output.gsasm`: 149 | 150 | ``` 151 | /home/xxx/gscript/main.gs(MainProto): 152 | MainFunc: 153 | 0 LOAD_CONST "hello world" 154 | 9 LOAD_BUILTIN "print" 155 | 14 CALL 0 1 156 | 17 STOP 157 | ``` 158 | 159 | + use `gsc run ` or `gsc run ` to run the script. 160 | 161 | ### References 162 | 163 | + [Language Syntax](https://github.com/gufeijun/gscript/blob/master/doc/syntax.md) 164 | + [Builtin Functions](https://github.com/gufeijun/gscript/blob/master/doc/builtin.md) 165 | + [Standard Library](https://github.com/gufeijun/gscript/blob/master/doc/std.md) 166 | + [BNF](https://github.com/gufeijun/gscript/blob/master/doc/bnf.txt) 167 | 168 | -------------------------------------------------------------------------------- /README_zh.md: -------------------------------------------------------------------------------- 1 | ## The Gscript Language 2 | 3 | 中文|[English](https://github.com/gufeijun/gscript/blob/master/README.md) 4 | 5 | Gscript是一个用纯Go编写的轻量动态脚本语言。 6 | 7 | 案例: 8 | 9 | ```python 10 | import fs 11 | import os 12 | 13 | # 定义函数 14 | func writeSomeMsg(filepath) { 15 | # 以只写模式打开文件,如果文件不存在则创建,如果文件存在则清空文件 16 | let file = fs.open(filepath,"wct"); 17 | 18 | # 往文件写入一些数据 19 | file.write("hello world!\n") 20 | file.write("gscript is a good language!") 21 | 22 | # 关闭文件 23 | file.close(); 24 | } 25 | 26 | let filepath = "./text.txt" 27 | 28 | try{ 29 | writeSomeMsg(filepath) 30 | 31 | let stat = fs.stat(filepath) 32 | print("size of text.txt is " + stat.size + "B"); 33 | 34 | # 读取文件所有内容 35 | let data = fs.readFile(filepath); 36 | print("message of", filepath, "is:") 37 | print(data.toString()) 38 | 39 | # 删除文件 40 | fs.remove(filepath) 41 | print("success!") 42 | } 43 | catch(e){ 44 | print("operation failed, error msg:",e) 45 | os.exit(0) 46 | } 47 | ``` 48 | 49 | ### 特性 50 | 51 | + 函数 52 | 53 | + 支持多返回值 54 | 55 | + 支持闭包 56 | + 支持函数递归 57 | 58 | + 脚本语言的所有标准库以静态资源的方式打包成单独一个二进制文件,gscript部署以及使用极为方便 59 | 60 | + 支持面向对象 61 | 62 | + 支持Debug模式调试代码 63 | 64 | + 能够生成高可读性的类汇编代码 65 | 66 | + 语法简单易学,特别是对于那些具有js和python经验的人 67 | 68 | + 多文件、模块化支持 69 | 70 | + 支持try, catch, throw异常处理机制 71 | 72 | + 编译生成字节码,使用VM执行 73 | 74 | + 完善的变量作用域机制 75 | 76 | ### 安装 77 | 78 | 由于gscript的编译器以及VM全部使用纯GO开发,不依赖CGO,未使用平台相关的接口,所以gscript本身是跨平台的语言。 79 | 80 | 两种安装gscript方式: 81 | 82 | + 源码编译 83 | 84 | *注意:由于使用了静态资源嵌入(embed)特性,所以保证你的GO编译器版本大于等于1.16*. 85 | 86 | ```shell 87 | git clone git@github.com:gufeijun/gscript.git 88 | cd gscript 89 | sh build.sh 90 | ``` 91 | 92 | `bin/gsc`就是生成的编译器二进制文件。 93 | 94 | + 从[releases](https://github.com/gufeijun/gscript/releases)下载。 95 | 96 | `gsc ` 意思是 `gscript compiler`。你可以自主选择是否将这个二进制文件添加到`PATH`中。 97 | 98 | ### 快速开始 99 | 100 | 在 `main.gs`写如下代码: 101 | 102 | ```python 103 | print("hello world"); 104 | ``` 105 | 106 | 运行脚本t: 107 | 108 | ```shell 109 | gsc run main.gs 110 | ``` 111 | 112 | 获得如下输出: 113 | 114 | ``` 115 | hello world 116 | ``` 117 | 118 | 你也能写更复杂的代码,计算fibonacci序列: 119 | 120 | ```python 121 | print(fib(20)) 122 | 123 | func fib(x) { 124 | if (x == 0) return 0; 125 | if (x == 1) return 1; 126 | return fib(x-1) + fib(x-2) 127 | } 128 | ``` 129 | 130 | 运行脚本,输出6765. 131 | 132 | ### 使用 133 | 134 | 我们上面已经演示了`gsc run`命令,你可以通过`gsc --help`获取更多使用帮助。 135 | 136 | + 使用 `gsc debug ` 或者 `gsc debug `进入调试模式,这个模式下能一行行执行字节码,并且能实时查看栈空间以及变量表的变化。 137 | 138 | + 使用 `gsc build ` 生成字节码,可以额外指定`-o`这个参数指定输出文件的名字。 139 | 140 | + 使用 `gsc build -a ` 或者 `gsc build -a ` 去生成高可读性的类汇编代码。 141 | 142 | 以上节的hello world脚本为例,运行如下命令: 143 | 144 | ```shell 145 | gsc build -a main.gs -o output.gscasm 146 | ``` 147 | 148 | 会生成文件 `output.gsasm`: 149 | 150 | ``` 151 | /home/xxx/gscript/main.gs(MainProto): 152 | MainFunc: 153 | 0 LOAD_CONST "hello world" 154 | 9 LOAD_BUILTIN "print" 155 | 14 CALL 0 1 156 | 17 STOP 157 | ``` 158 | 159 | + 使用 `gsc run ` 或者 `gsc run `运行脚本或者字节码。 160 | 161 | ### 参考 162 | 163 | + [语法](https://github.com/gufeijun/gscript/blob/master/doc/syntax_zh.md) 164 | + [内置函数](https://github.com/gufeijun/gscript/blob/master/doc/builtin.md) 165 | + [标准库](https://github.com/gufeijun/gscript/blob/master/doc/std.md) 166 | + [文法](https://github.com/gufeijun/gscript/blob/master/doc/bnf.txt) 167 | 168 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | version=$(go version | awk '{print $3}' | awk '{split($0,b,".");print b[2]}') 4 | if [ $version -lt 16 ]; then 5 | echo "[failed] go version should be greater than or equal to 1.16" 6 | exit 7 | fi 8 | 9 | cd $(dirname $0) 10 | 11 | touch std/.gsproto 12 | 13 | prepare() { 14 | files=$(ls std/*.gsproto 2> /dev/null | wc -l) 15 | if [ "$files" = "0" ]; then 16 | touch std/.gsproto # make go compiler happy for embedding at least one static file 17 | fi 18 | 19 | if [ ! -d "bin" ]; then 20 | mkdir bin 21 | fi 22 | } 23 | 24 | build_util() { 25 | prepare 26 | go build -o bin/util util/main.go 27 | bin/util std 28 | } 29 | 30 | if [ $# -eq 0 ]; then 31 | build_util 32 | go build -o bin/gsc main.go 33 | exit 0 34 | fi 35 | 36 | # $1=arch $2=os 37 | build() { 38 | go env -w GOARCH=$1 39 | go env -w GOOS=$2 40 | util_bin=util 41 | gsc_bin=gsc 42 | if [ $2 = "windows" ]; then 43 | util_bin=${util_bin}.exe 44 | gsc_bin=${gsc_bin}.exe 45 | fi 46 | 47 | go build -o bin/${gsc_bin} main.go 48 | cd bin 49 | if [ $2 = "windows" ]; then 50 | zip gsc_$2_$1.zip ${gsc_bin} 51 | else 52 | tar cvf gsc_$2_$1.tar.gz ${gsc_bin} 53 | fi 54 | cd .. 55 | 56 | } 57 | 58 | if [ $1 = "all" ]; then 59 | build_util 60 | old_os=$(go env GOOS) 61 | old_arch=$(go env GOARCH) 62 | 63 | build "amd64" "windows" 64 | build "arm" "windows" 65 | build "386" "windows" 66 | build "amd64" "linux" 67 | build "arm" "linux" 68 | build "386" "linux" 69 | build "amd64" "darwin" 70 | 71 | go env -w GOOS=$old_os 72 | go env -w GOARCH=$old_arch 73 | elif [ $1 = "clean" ]; then 74 | rm -rf bin std/*.gsproto std/.gsproto 75 | else 76 | echo "unkown command:" $1 77 | exit 78 | fi 79 | -------------------------------------------------------------------------------- /cmd/asm.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | "gscript/proto" 8 | "gscript/std" 9 | "gscript/vm" 10 | "io" 11 | "os" 12 | ) 13 | 14 | func WriteHumanReadableAsmToFile(target string, protos []proto.Proto) error { 15 | file, err := os.OpenFile(target, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0664) 16 | if err != nil { 17 | return err 18 | } 19 | defer file.Close() 20 | return WriteHumanReadableAsm(file, protos) 21 | } 22 | 23 | func WriteHumanReadableAsm(w io.Writer, protos []proto.Proto) error { 24 | for i := range protos { 25 | var buff bytes.Buffer 26 | if err := writeHumanReadableAsm(&buff, protos, i); err != nil { 27 | return err 28 | } 29 | if _, err := io.Copy(w, &buff); err != nil { 30 | return err 31 | } 32 | } 33 | return nil 34 | } 35 | 36 | func writeHumanReadableAsm(w *bytes.Buffer, protos []proto.Proto, idx int) error { 37 | p := protos[idx] 38 | if idx == 0 { 39 | fmt.Fprintf(w, "%s(MainProto):\n", p.FilePath) 40 | } else { 41 | fmt.Fprintf(w, "%s(%d):\n", p.FilePath, idx) 42 | } 43 | fmt.Fprintf(w, "\tMainFunc:\n") 44 | if err := writeAsm(w, p.Text, protos); err != nil { 45 | return err 46 | } 47 | if err := writeFuncs(w, p.Funcs, protos); err != nil { 48 | return err 49 | } 50 | if err := writeAnonymousFuncs(w, p.AnonymousFuncs, protos); err != nil { 51 | return err 52 | } 53 | // should write constant table? 54 | return nil 55 | } 56 | 57 | func writeAnonymousFuncs(w *bytes.Buffer, funcs []proto.AnonymousFuncProto, protos []proto.Proto) error { 58 | for i := range funcs { 59 | fmt.Fprintf(w, "\t%dth AnonymousFunc:\n", i) 60 | if err := writeAsm(w, funcs[i].Info.Text, protos); err != nil { 61 | return err 62 | } 63 | } 64 | return nil 65 | } 66 | 67 | func writeFuncs(w *bytes.Buffer, funcs []proto.FuncProto, protos []proto.Proto) error { 68 | for i := range funcs { 69 | fmt.Fprintf(w, "\t%dth NamedFunc \"%s\":\n", i, funcs[i].Name) 70 | if err := writeAsm(w, funcs[i].Info.Text, protos); err != nil { 71 | return err 72 | } 73 | } 74 | return nil 75 | } 76 | 77 | func getUint32(pc *int, text []byte) uint32 { 78 | num := binary.LittleEndian.Uint32(text[*pc:]) 79 | *pc += 4 80 | return num 81 | } 82 | 83 | func writeEscapeString(w *bytes.Buffer, str string) { 84 | w.WriteByte('"') 85 | for _, ch := range str { 86 | if ch == '\r' { 87 | w.WriteString("\\r") 88 | } else if ch == '\n' { 89 | w.WriteString("\\n") 90 | } else if ch == '\t' { 91 | w.WriteString("\\t") 92 | } else { 93 | w.WriteRune(ch) 94 | } 95 | } 96 | w.WriteByte('"') 97 | } 98 | 99 | func writeAsm(w *bytes.Buffer, text []byte, protos []proto.Proto) error { 100 | getFuncName := func(protoNum uint32, idx uint32) string { 101 | return protos[protoNum].Funcs[idx].Name 102 | } 103 | getFileName := func(protoNum uint32) string { 104 | return protos[protoNum].FilePath 105 | } 106 | var pc int 107 | for { 108 | if pc >= len(text) { 109 | break 110 | } 111 | fmt.Fprintf(w, "\t\t") 112 | fmt.Fprintf(w, "%d\t\t", pc) 113 | instruction := text[pc] 114 | pc++ 115 | if int(instruction) < len(zereOpNumAsms) { 116 | fmt.Fprintf(w, "%s\n", zereOpNumAsms[instruction]) 117 | continue 118 | } 119 | switch instruction { 120 | case proto.INS_LOAD_CONST: 121 | protoNum, idx := getUint32(&pc, text), getUint32(&pc, text) 122 | fmt.Fprintf(w, "LOAD_CONST ") 123 | cons := protos[protoNum].Consts[idx] 124 | if str, ok := cons.(string); ok { 125 | writeEscapeString(w, str) 126 | } else { 127 | fmt.Fprintf(w, "%v", cons) 128 | } 129 | case proto.INS_LOAD_NAME: 130 | idx := getUint32(&pc, text) 131 | fmt.Fprintf(w, "LOAD_NAME %d", idx) 132 | case proto.INS_LOAD_FUNC: 133 | protoNum, idx := getUint32(&pc, text), getUint32(&pc, text) 134 | fmt.Fprintf(w, "LOAD_FUNC \"%s\"", getFuncName(protoNum, idx)) 135 | case proto.INS_LOAD_BUILTIN: 136 | idx := getUint32(&pc, text) 137 | fmt.Fprintf(w, "LOAD_BUILTIN \"%s\"", vm.GetBuiltinFuncNameByNum(idx)) 138 | case proto.INS_LOAD_ANONYMOUS: 139 | _, idx := getUint32(&pc, text), getUint32(&pc, text) 140 | fmt.Fprintf(w, "LOAD_ANONYMOUS %d", idx) 141 | case proto.INS_LOAD_UPVALUE: 142 | idx := getUint32(&pc, text) 143 | fmt.Fprintf(w, "LOAD_UPVALUE %d", idx) 144 | case proto.INS_LOAD_PROTO: 145 | protoNum := getUint32(&pc, text) 146 | fmt.Fprintf(w, "LOAD_PROTO %d(%s)", protoNum, getFileName(protoNum)) 147 | case proto.INS_LOAD_STDLIB: 148 | idx := getUint32(&pc, text) 149 | fmt.Fprintf(w, "LOAD_STDLIB %s", std.GetLibNameByProtoNum(idx)) 150 | case proto.INS_STORE_NAME: 151 | idx := getUint32(&pc, text) 152 | fmt.Fprintf(w, "STORE_NAME %d", idx) 153 | case proto.INS_STORE_UPVALUE: 154 | idx := getUint32(&pc, text) 155 | fmt.Fprintf(w, "STORE_UPVALUE %d", idx) 156 | case proto.INS_RESIZE_NAMETABLE: 157 | idx := getUint32(&pc, text) 158 | fmt.Fprintf(w, "RESIZE_TABLE %d", idx) 159 | case proto.INS_SLICE_NEW: 160 | idx := getUint32(&pc, text) 161 | fmt.Fprintf(w, "SLICE_NEW %d", idx) 162 | case proto.INS_NEW_MAP: 163 | idx := getUint32(&pc, text) 164 | fmt.Fprintf(w, "MAP_NEW %d", idx) 165 | case proto.INS_JUMP_REL: 166 | steps := getUint32(&pc, text) 167 | fmt.Fprintf(w, "JUMP %d", pc+int(steps)) 168 | case proto.INS_JUMP_ABS: 169 | addr := getUint32(&pc, text) 170 | fmt.Fprintf(w, "JUMP %d", addr) 171 | case proto.INS_JUMP_IF: 172 | steps := getUint32(&pc, text) 173 | fmt.Fprintf(w, "JUMP_IF %d", pc+int(steps)) 174 | case proto.INS_JUMP_LAND: 175 | steps := getUint32(&pc, text) 176 | fmt.Fprintf(w, "JUMP_LAND %d", pc+int(steps)) 177 | case proto.INS_JUMP_LOR: 178 | steps := getUint32(&pc, text) 179 | fmt.Fprintf(w, "JUMP_LOR %d", pc+int(steps)) 180 | case proto.INS_JUMP_CASE: 181 | steps := getUint32(&pc, text) 182 | fmt.Fprintf(w, "JUMP_CASE %d", pc+int(steps)) 183 | case proto.INS_CALL: 184 | retCnt, argCnt := text[pc], text[pc+1] 185 | pc += 2 186 | fmt.Fprintf(w, "CALL %d %d", retCnt, argCnt) 187 | case proto.INS_RETURN: 188 | retCnt := getUint32(&pc, text) 189 | fmt.Fprintf(w, "RETURN %d", retCnt) 190 | case proto.INS_TRY: 191 | steps := getUint32(&pc, text) 192 | fmt.Fprintf(w, "TRY %d", pc+int(steps)) 193 | default: 194 | return fmt.Errorf("invalid instruction code: %d", instruction) 195 | } 196 | fmt.Fprintln(w) 197 | } 198 | fmt.Fprintln(w) 199 | return nil 200 | } 201 | 202 | var zereOpNumAsms = []string{ 203 | "NOT", "NEG", "LNOT", "ADD", "SUB", "MUL", "DIV", "MOD", "AND", 204 | "XOR", "OR", "IDIV", "SHR", "SHL", "LE", "GE", "LT", "GT", "EQ", 205 | "NE", "LAND", "LOR", "ATTR", "LOAD_NIL", "STORE_KV", "PUSH_NIL", 206 | "PUSH_NAME", "COPY_STACK_TOP", "POP_TOP", "STOP", "ATTR=", "ATTR+=", 207 | "ATTR-=", "ATTR*=", "ATTR/=", "ATTR%=", "ATTR&=", "ATTR^=", "ATTR|=", 208 | "ATTR_ACCESS", "ROT_TWO", "EXPORT", "END_TRY", "NEW_EMPTY_MAP", 209 | } 210 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "gscript/compiler" 6 | "gscript/proto" 7 | "gscript/std" 8 | "gscript/vm" 9 | "os" 10 | "path" 11 | "strings" 12 | 13 | "github.com/spf13/cobra" 14 | ) 15 | 16 | var ( 17 | Flag_Output string 18 | Flag_Asm bool 19 | ) 20 | 21 | var rootCmd = &cobra.Command{ 22 | Use: "gsc", 23 | Args: cobra.MinimumNArgs(1), 24 | } 25 | 26 | var versionCmd = &cobra.Command{ 27 | Use: "version", 28 | Short: "Show Version", 29 | Args: cobra.NoArgs, 30 | DisableFlagParsing: true, 31 | DisableFlagsInUseLine: true, 32 | Run: func(cmd *cobra.Command, args []string) { 33 | fmt.Printf("%d.%d\n", proto.VersionMajor, proto.VersionMinor) 34 | }, 35 | } 36 | 37 | var runCmd = &cobra.Command{ 38 | Use: "run ", 39 | Short: "Excute script file. Usage: gsc run ", 40 | Args: cobra.MinimumNArgs(1), 41 | FParseErrWhitelist: cobra.FParseErrWhitelist{ 42 | UnknownFlags: true, 43 | }, 44 | DisableFlagsInUseLine: true, 45 | Run: func(cmd *cobra.Command, args []string) { 46 | vm, err := initVM(args[0]) 47 | if err != nil { 48 | exit(err) 49 | } 50 | vm.Run() 51 | }, 52 | } 53 | 54 | var debugCmd = &cobra.Command{ 55 | Use: "debug ", 56 | Short: "Debug script file. Usage: gsc debug ", 57 | Args: cobra.MinimumNArgs(1), 58 | FParseErrWhitelist: cobra.FParseErrWhitelist{ 59 | UnknownFlags: true, 60 | }, 61 | DisableFlagsInUseLine: true, 62 | Run: func(cmd *cobra.Command, args []string) { 63 | vm, err := initVM(args[0]) 64 | if err != nil { 65 | exit(err) 66 | } 67 | vm.Debug() 68 | }, 69 | } 70 | 71 | var buildCmd = &cobra.Command{ 72 | Use: "build [flags] ", 73 | Short: "complie srcipt file. Usage: gsc build [flags] ", 74 | Args: cobra.ExactArgs(1), 75 | DisableFlagsInUseLine: true, 76 | Run: func(cmd *cobra.Command, args []string) { 77 | src := args[0] 78 | // just complie source file to bytes code 79 | if !Flag_Asm { 80 | exit(complieToBytesCode(src)) 81 | } 82 | // generate human-readable assemble code 83 | exit(complieToReadableAsm(src)) 84 | }, 85 | } 86 | 87 | func complieToReadableAsm(src string) (err error) { 88 | var protos []proto.Proto 89 | if proto.IsProtoFile(src) { 90 | _, protos, err = proto.ReadProtosFromFile(src) 91 | } else { 92 | protos, err = compiler.ComplieWithSrcFile(src) 93 | } 94 | if err != nil { 95 | return err 96 | } 97 | if Flag_Output == "" { 98 | Flag_Output = replaceExtension(src, ".gsasm") 99 | } 100 | return WriteHumanReadableAsmToFile(Flag_Output, protos) 101 | } 102 | 103 | func complieToBytesCode(src string) error { 104 | if Flag_Output == "" { 105 | Flag_Output = replaceExtension(src, std.ProtoSuffix) 106 | } 107 | protos, err := compiler.ComplieWithSrcFile(src) 108 | if err != nil { 109 | return err 110 | } 111 | return proto.WriteProtosToFile(Flag_Output, protos) 112 | } 113 | 114 | func replaceExtension(src string, extension string) string { 115 | return strings.TrimSuffix(src, path.Ext(path.Base(src))) + extension 116 | } 117 | 118 | func initVM(src string) (*vm.VM, error) { 119 | var protos []proto.Proto 120 | var err error 121 | if proto.IsProtoFile(src) { 122 | _, protos, err = proto.ReadProtosFromFile(src) 123 | } else { 124 | protos, err = compiler.ComplieWithSrcFile(src) 125 | } 126 | if err != nil { 127 | return nil, err 128 | } 129 | 130 | stdlibs, err := std.ReadProtos() 131 | if err != nil { 132 | return nil, err 133 | } 134 | v := vm.NewVM(protos, stdlibs) 135 | return v, nil 136 | } 137 | 138 | func init() { 139 | buildCmd.Flags().StringVarP(&Flag_Output, "output", "o", "", "output file") 140 | buildCmd.Flags().BoolVarP(&Flag_Asm, "asm", "a", false, "output human-readable assembly code") 141 | rootCmd.AddCommand(runCmd, versionCmd, debugCmd, buildCmd) 142 | } 143 | 144 | func Execute() { 145 | if err := rootCmd.Execute(); err != nil { 146 | fmt.Fprintln(os.Stderr, err) 147 | os.Exit(1) 148 | } 149 | } 150 | 151 | func exit(err error) { 152 | if err == nil { 153 | return 154 | } 155 | fmt.Printf("Failed: %s\n", err.Error()) 156 | os.Exit(0) 157 | } 158 | -------------------------------------------------------------------------------- /compiler/ast/exp.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | type Exp interface{} 4 | 5 | type MapLiteralExp struct { 6 | Keys []interface{} // true, false, STRING, NUMBER 7 | Vals []Exp 8 | } 9 | 10 | type ArrLiteralExp struct { 11 | Vals []Exp 12 | } 13 | 14 | type StringLiteralExp struct { 15 | Value string 16 | } 17 | 18 | type NumberLiteralExp struct { 19 | Value interface{} 20 | } 21 | 22 | type FuncLiteralExp struct { 23 | FuncLiteral 24 | } 25 | 26 | type FalseExp struct{} 27 | 28 | type TrueExp struct{} 29 | 30 | type NilExp struct{} 31 | 32 | type NewObjectExp struct { 33 | Line int 34 | Name string 35 | Args []Exp 36 | } 37 | 38 | type NameExp struct { 39 | Line int 40 | Name string 41 | } 42 | 43 | type UnOpExp struct { 44 | Op int 45 | Exp Exp 46 | } 47 | 48 | // . ==> [] 49 | // a op b or a[b] or a.b 50 | type BinOpExp struct { 51 | BinOp int 52 | Exp1 Exp 53 | Exp2 Exp 54 | } 55 | 56 | // a==b ? a:b 57 | type TernaryOpExp struct { 58 | Exp1, Exp2, Exp3 Exp 59 | } 60 | 61 | type FuncCallExp struct { 62 | Func Exp 63 | Args []Exp 64 | } 65 | -------------------------------------------------------------------------------- /compiler/ast/op.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import "gscript/compiler/token" 4 | 5 | // do not change the order of following const 6 | const ( 7 | ASIGN_OP_START = token.TOKEN_ASIGN_START + iota 8 | ASIGN_OP_ASSIGN // = 9 | ASIGN_OP_ADDEQ // += 10 | ASIGN_OP_SUBEQ // -= 11 | ASIGN_OP_MULEQ // *= 12 | ASIGN_OP_DIVEQ // /= 13 | ASIGN_OP_MODEQ // %= 14 | ASIGN_OP_ANDEQ // &= 15 | ASIGN_OP_XOREQ // ^= 16 | ASIGN_OP_OREQ // |= 17 | ) 18 | 19 | const ( 20 | BINOP_START = token.TOKEN_BINOP_START + iota 21 | BINOP_ADD // + 22 | BINOP_SUB // - 23 | BINOP_MUL // * 24 | BINOP_DIV // / 25 | BINOP_MOD // % 26 | BINOP_AND // & 27 | BINOP_XOR // ^ 28 | BINOP_OR // | 29 | BINOP_IDIV // // 30 | BINOP_SHR // >> 31 | BINOP_SHL // << 32 | BINOP_LE // <= 33 | BINOP_GE // >= 34 | BINOP_LT // < 35 | BINOP_GT // > 36 | BINOP_EQ // == 37 | BINOP_NE // != 38 | BINOP_LAND // && 39 | BINOP_LOR // || 40 | BINOP_ATTR // [] 41 | ) 42 | 43 | const ( 44 | UNOP_NOT = iota // ~ 45 | UNOP_LNOT // ! 46 | UNOP_NEG // - 47 | UNOP_DEC // --i 48 | UNOP_INC // ++i 49 | UNOP_DEC_ // i-- 50 | UNOP_INC_ // i++ 51 | ) 52 | -------------------------------------------------------------------------------- /compiler/ast/program.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | // AST root 4 | type Program struct { 5 | File string // source file 6 | Imports []Import 7 | BlockStmts []BlockStmt 8 | Export Export 9 | } 10 | 11 | // import net,http as n,h 12 | type Import struct { 13 | Line int 14 | Libs []Lib 15 | } 16 | 17 | type Lib struct { 18 | Stdlib bool 19 | Path string 20 | Alias string 21 | } 22 | 23 | // Block or Stmt 24 | type BlockStmt interface{} 25 | 26 | type Block struct { 27 | Blocks []BlockStmt 28 | } 29 | 30 | type Export struct { 31 | Exp Exp 32 | } 33 | -------------------------------------------------------------------------------- /compiler/ast/stmt.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | type Stmt interface{} 4 | 5 | type Var struct { 6 | Prefix string 7 | Attrs []Exp 8 | } 9 | 10 | // let a,b,c = 1, "hello", add(1,2) 11 | type VarDeclStmt struct { 12 | Line int 13 | Lefts []string 14 | Rights []Exp 15 | } 16 | 17 | // number, map["key"] = 2,"value" 18 | // obj.total, i += 1,2 19 | // i++ ==> i += 1 20 | type VarAssignStmt struct { 21 | Line int 22 | AssignOp int 23 | Lefts []Var 24 | Rights []Exp 25 | } 26 | 27 | // function call may like this: 28 | // arr[1].FuncMap["Handlers"]("Sum")(1,2) 29 | type NamedFuncCallStmt struct { 30 | Prefix string 31 | CallTails []CallTail 32 | } 33 | 34 | type CallTail struct { 35 | Attrs []Exp 36 | Args []Exp 37 | } 38 | 39 | type LabelStmt struct { 40 | Name string 41 | } 42 | 43 | // func sum(...arr) 44 | // func add(a,b=1) 45 | type FuncDefStmt struct { 46 | Name string 47 | FuncLiteral 48 | } 49 | 50 | type FuncLiteral struct { 51 | Line int 52 | Parameters []Parameter 53 | VaArgs string 54 | Block Block 55 | } 56 | 57 | type Parameter struct { 58 | Name string // parameter name 59 | Default interface{} // string, number, true or false 60 | } 61 | 62 | type AnonymousFuncCallStmt struct { 63 | FuncLiteral 64 | CallTails []CallTail 65 | } 66 | 67 | type BreakStmt struct { 68 | Line int 69 | } 70 | 71 | type ContinueStmt struct { 72 | Line int 73 | } 74 | 75 | type GotoStmt struct { 76 | Line int 77 | Label string 78 | } 79 | 80 | type FallthroughStmt struct { 81 | Line int 82 | } 83 | 84 | type WhileStmt struct { 85 | Condition Exp 86 | Block Block 87 | } 88 | 89 | type ForStmt struct { 90 | // only one of these two is not nil 91 | AsgnStmt *VarAssignStmt 92 | DeclStmt *VarDeclStmt 93 | 94 | Condition Exp 95 | ForTail *VarAssignStmt 96 | Block Block 97 | } 98 | 99 | type LoopStmt struct { 100 | Key string 101 | Val string 102 | Iterator Exp 103 | Block Block 104 | } 105 | 106 | // else ==> elif(true) 107 | type IfStmt struct { 108 | Conditions []Exp 109 | Blocks []Block 110 | } 111 | 112 | type ClassStmt struct { 113 | Name string 114 | AttrName []string 115 | AttrValue []Exp 116 | Constructor *FuncLiteralExp 117 | } 118 | 119 | type EnumStmt struct { 120 | Names []string 121 | Lines []int 122 | Values []int64 123 | } 124 | 125 | type SwitchStmt struct { 126 | Value Exp 127 | Cases [][]Exp 128 | Blocks [][]BlockStmt 129 | Default []BlockStmt 130 | } 131 | 132 | type ReturnStmt struct { 133 | Args []Exp 134 | } 135 | 136 | type TryCatchStmt struct { 137 | TryBlocks []BlockStmt 138 | CatchValue string 139 | CatchLine int 140 | CatchBlocks []BlockStmt 141 | } 142 | -------------------------------------------------------------------------------- /compiler/codegen/const_table.go: -------------------------------------------------------------------------------- 1 | package codegen 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | ) 7 | 8 | type ConstTable struct { 9 | Constants []interface{} 10 | ConsMap map[interface{}]uint32 // constant -> constants index 11 | enums map[string]enum // enum -> constants index 12 | } 13 | 14 | type enum struct { 15 | idx uint32 16 | line uint32 17 | } 18 | 19 | func newConstTable() *ConstTable { 20 | return &ConstTable{ 21 | ConsMap: make(map[interface{}]uint32), 22 | enums: make(map[string]enum), 23 | } 24 | } 25 | 26 | func (ct *ConstTable) saveEnum(name string, line int, num int64) { 27 | if exists, ok := ct.enums[name]; ok { 28 | fmt.Printf("[%s] enum name '%s' already defines at line %d, but redeclares at line %d", curParsingFile, name, exists.line, line) 29 | os.Exit(0) 30 | } 31 | ct.enums[name] = enum{ 32 | idx: uint32(len(ct.Constants)), 33 | line: uint32(line), 34 | } 35 | ct.Constants = append(ct.Constants, num) 36 | } 37 | 38 | func (ct *ConstTable) getEnum(name string) (idx uint32, ok bool) { 39 | enum, ok := ct.enums[name] 40 | return enum.idx, ok 41 | } 42 | 43 | func (ct *ConstTable) Get(key interface{}) uint32 { 44 | if idx, ok := ct.ConsMap[key]; ok { 45 | return idx 46 | } 47 | ct.Constants = append(ct.Constants, key) 48 | idx := uint32(len(ct.Constants) - 1) 49 | ct.ConsMap[key] = idx 50 | return idx 51 | } 52 | -------------------------------------------------------------------------------- /compiler/codegen/context.go: -------------------------------------------------------------------------------- 1 | package codegen 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "gscript/compiler/ast" 7 | "gscript/compiler/parser" 8 | "gscript/proto" 9 | "os" 10 | ) 11 | 12 | type Context struct { 13 | protoNum uint32 14 | parser *parser.Parser 15 | ct *ConstTable 16 | ft *FuncTable 17 | classes map[string]uint32 // class name -> FuncTable index 18 | 19 | frame *StackFrame 20 | } 21 | 22 | func newContext(parser *parser.Parser) *Context { 23 | ft := newFuncTable(parser.FuncDefs) 24 | return &Context{ 25 | parser: parser, 26 | ct: newConstTable(), 27 | ft: ft, 28 | classes: newClassTable(parser.ClassStmts, ft), 29 | frame: newStackFrame(), 30 | } 31 | } 32 | 33 | func newClassTable(stmts []*ast.ClassStmt, ft *FuncTable) map[string]uint32 { 34 | classes := map[string]uint32{} 35 | ft.anonymousFuncs = make([]proto.AnonymousFuncProto, len(stmts)) 36 | for i, stmt := range stmts { 37 | classes[stmt.Name] = uint32(i) 38 | info := &proto.BasicInfo{} 39 | __self := stmt.Constructor 40 | if __self != nil { 41 | info.Parameters = __self.Parameters 42 | info.VaArgs = __self.VaArgs != "" 43 | } 44 | ft.anonymousFuncs[i].Info = info 45 | } 46 | return classes 47 | } 48 | 49 | func (ctx *Context) pushFrame(anonymous bool, idx int) { 50 | frame := newStackFrame() 51 | frame.prev = ctx.frame 52 | ctx.frame = frame 53 | } 54 | 55 | func (ctx *Context) popFrame() *StackFrame { 56 | frame := ctx.frame 57 | ctx.frame = ctx.frame.prev 58 | return frame 59 | } 60 | 61 | func (ctx *Context) writeIns(ins byte) { 62 | ctx.frame.text = append(ctx.frame.text, ins) 63 | } 64 | 65 | func (ctx *Context) writeUint(idx uint32) { 66 | var arr [4]byte 67 | binary.LittleEndian.PutUint32(arr[:], idx) 68 | ctx.frame.text = append(ctx.frame.text, arr[:]...) 69 | } 70 | 71 | func (ctx *Context) writeByte(v byte) { 72 | ctx.frame.text = append(ctx.frame.text, v) 73 | } 74 | 75 | func (ctx *Context) insCopyName(name string) { 76 | ctx.writeIns(proto.INS_COPY_STACK_TOP) 77 | ctx.frame.nt.Set(name, 0) 78 | } 79 | 80 | func (ctx *Context) insPushName(name string, line uint32) { 81 | ctx.writeIns(proto.INS_PUSH_NAME) 82 | ctx.frame.nt.Set(name, line) 83 | } 84 | 85 | func (ctx *Context) insCall(wantRtnCnt byte, argCnt byte) { 86 | ctx.writeIns(proto.INS_CALL) 87 | ctx.writeByte(wantRtnCnt) 88 | ctx.writeByte(argCnt) 89 | } 90 | 91 | func (ctx *Context) insTry(catchAddr uint32) int { 92 | ctx.writeIns(proto.INS_TRY) 93 | pos := len(ctx.frame.text) 94 | ctx.writeUint(catchAddr) 95 | return pos 96 | } 97 | 98 | func (ctx *Context) insReturn(argCnt uint32) { 99 | ctx.writeIns(proto.INS_RETURN) 100 | ctx.writeUint(argCnt) 101 | } 102 | 103 | func (ctx *Context) insResizeNameTable(size uint32) int { 104 | ctx.writeIns(proto.INS_RESIZE_NAMETABLE) 105 | pos := len(ctx.frame.text) 106 | ctx.writeUint(size) 107 | return pos 108 | } 109 | 110 | func (ctx *Context) setAddr(pos int, addr uint32) { 111 | binary.LittleEndian.PutUint32(ctx.frame.text[pos:pos+4], addr) 112 | } 113 | 114 | func (ctx *Context) setSteps(pos int, addr uint32) { 115 | binary.LittleEndian.PutUint32(ctx.frame.text[pos:pos+4], addr-uint32(pos+4)) 116 | } 117 | 118 | func (ctx *Context) insPopTop() { 119 | ctx.writeIns(proto.INS_POP_TOP) 120 | } 121 | 122 | func (ctx *Context) insJumpCase(addr uint32) int { 123 | ctx.writeIns(proto.INS_JUMP_CASE) 124 | pos := len(ctx.frame.text) 125 | ctx.writeUint(addr - uint32(pos+4)) 126 | return pos 127 | } 128 | 129 | func (ctx *Context) insJumpIfLAnd(addr uint32) int { 130 | ctx.writeIns(proto.INS_JUMP_LAND) 131 | pos := len(ctx.frame.text) 132 | ctx.writeUint(addr - uint32(pos+4)) 133 | return pos 134 | } 135 | 136 | func (ctx *Context) insJumpIfLOr(addr uint32) int { 137 | ctx.writeIns(proto.INS_JUMP_LOR) 138 | pos := len(ctx.frame.text) 139 | ctx.writeUint(addr - uint32(pos+4)) 140 | return pos 141 | } 142 | 143 | func (ctx *Context) insJumpIf(addr uint32) int { 144 | ctx.writeIns(proto.INS_JUMP_IF) 145 | pos := len(ctx.frame.text) 146 | ctx.writeUint(addr - uint32(pos+4)) 147 | return pos 148 | } 149 | 150 | func (ctx *Context) insJumpRel(addr uint32) int { 151 | ctx.writeIns(proto.INS_JUMP_REL) 152 | pos := len(ctx.frame.text) 153 | ctx.writeUint(addr - uint32(pos+4)) 154 | return pos 155 | } 156 | 157 | func (ctx *Context) insJumpAbs(addr uint32) int { 158 | ctx.writeIns(proto.INS_JUMP_ABS) 159 | pos := len(ctx.frame.text) 160 | ctx.writeUint(addr) 161 | return pos 162 | } 163 | 164 | func (ctx *Context) insLoadConst(c interface{}) { 165 | idx := ctx.ct.Get(c) 166 | if stdLibGenMode { 167 | ctx.writeIns(proto.INS_LOAD_STD_CONST) 168 | } else { 169 | ctx.writeIns(proto.INS_LOAD_CONST) 170 | } 171 | ctx.writeUint(ctx.protoNum) 172 | ctx.writeUint(idx) 173 | } 174 | 175 | func (ctx *Context) insLoadNil() { 176 | ctx.writeIns(proto.INS_LOAD_NIL) 177 | } 178 | 179 | func (ctx *Context) insLoadFunc(idx uint32) { 180 | if stdLibGenMode { 181 | ctx.writeIns(proto.INS_LOAD_STD_FUNC) 182 | } else { 183 | ctx.writeIns(proto.INS_LOAD_FUNC) 184 | } 185 | ctx.writeUint(ctx.protoNum) 186 | ctx.writeUint(idx) 187 | } 188 | 189 | func (ctx *Context) insLoadAnonymous(idx uint32) { 190 | if stdLibGenMode { 191 | ctx.writeIns(proto.INS_LOAD_STD_ANONYMOUS) 192 | } else { 193 | ctx.writeIns(proto.INS_LOAD_ANONYMOUS) 194 | } 195 | ctx.writeUint(ctx.protoNum) 196 | ctx.writeUint(idx) 197 | } 198 | 199 | func (ctx *Context) insLoadUpValue(idx uint32) { 200 | ctx.writeIns(proto.INS_LOAD_UPVALUE) 201 | ctx.writeUint(idx) 202 | } 203 | 204 | func (ctx *Context) insLoadProto(idx uint32) { 205 | ctx.writeIns(proto.INS_LOAD_PROTO) 206 | ctx.writeUint(idx) 207 | } 208 | 209 | func (ctx *Context) insLoadStdlib(idx uint32) { 210 | ctx.writeIns(proto.INS_LOAD_STDLIB) 211 | ctx.writeUint(idx) 212 | } 213 | 214 | func (ctx *Context) insStoreUpValue(idx uint32) { 215 | ctx.writeIns(proto.INS_STORE_UPVALUE) 216 | ctx.writeUint(idx) 217 | } 218 | 219 | func searchFrame(frame *StackFrame, name string) (uint32, bool) { 220 | for nt := frame.nt; nt != nil; nt = nt.prev { 221 | if v, ok := nt.nameTable[name]; ok { 222 | return v.idx, true 223 | } 224 | } 225 | return 0, false 226 | } 227 | 228 | func tryLoadUpValue(ctx *Context, name string) (upValueIdx uint32, ok bool) { 229 | upValueIdx, _, ok = ctx.frame.vt.get(name) 230 | if ok { 231 | return upValueIdx, true 232 | } 233 | var level uint32 = 0 234 | for frame := ctx.frame.prev; frame != nil; frame = frame.prev { 235 | idx, ok := searchFrame(frame, name) 236 | if ok { 237 | upValueIdx = ctx.frame.vt.set(name, level, idx) 238 | return upValueIdx, true 239 | } 240 | level++ 241 | } 242 | return 0, false 243 | } 244 | 245 | func (ctx *Context) insLoadName(name string, line int) { 246 | // ctx.frame.nt.nameTable 247 | 248 | // name is a defined variable? 249 | idx, ok := searchFrame(ctx.frame, name) 250 | if ok { 251 | ctx.writeIns(proto.INS_LOAD_NAME) 252 | ctx.writeUint(idx) 253 | return 254 | } 255 | 256 | // name is an upValue? 257 | if idx, ok := tryLoadUpValue(ctx, name); ok { 258 | ctx.insLoadUpValue(idx) 259 | return 260 | } 261 | 262 | // name is a function? 263 | idx, ok = ctx.ft.funcMap[name] 264 | if ok { 265 | ctx.insLoadFunc(idx) 266 | return 267 | } 268 | 269 | // name is a enum constant? 270 | idx, ok = ctx.ct.getEnum(name) 271 | if ok { 272 | ctx.writeIns(proto.INS_LOAD_CONST) 273 | ctx.writeUint(ctx.protoNum) 274 | ctx.writeUint(idx) 275 | return 276 | } 277 | 278 | // name is a builtin function? 279 | idx, ok = builtinFuncs[name] 280 | if ok { 281 | ctx.writeIns(proto.INS_LOAD_BUILTIN) 282 | ctx.writeUint(idx) 283 | return 284 | } 285 | 286 | fmt.Printf("[%s:%d] undeclared name '%s'\n", curParsingFile, line, name) 287 | os.Exit(0) 288 | } 289 | 290 | func (ctx *Context) insStoreName(name string, line int) { 291 | idx, ok := ctx.frame.nt.get(name) 292 | if ok { 293 | ctx.writeIns(proto.INS_STORE_NAME) 294 | ctx.writeUint(idx) 295 | return 296 | } 297 | 298 | // if name is an upvalue? 299 | if idx, ok := tryLoadUpValue(ctx, name); ok { 300 | ctx.insStoreUpValue(idx) 301 | return 302 | } 303 | 304 | fmt.Printf("[%s:%d] undeclared name '%s'\n", curParsingFile, line, name) 305 | os.Exit(0) 306 | } 307 | 308 | func (ctx *Context) enterBlock() { 309 | nt := newNameTable(ctx.frame.nt.nameIdx) 310 | nt.prev = ctx.frame.nt 311 | ctx.frame.nt = nt 312 | } 313 | 314 | func (ctx *Context) leaveBlock(size uint32, varDecl bool) { 315 | nt := ctx.frame.nt 316 | ctx.frame.nt = nt.prev 317 | *ctx.frame.nt.nameIdx = size 318 | if varDecl { 319 | ctx.insResizeNameTable(size) 320 | } 321 | } 322 | 323 | func (ctx *Context) textSize() uint32 { 324 | return uint32(len(ctx.frame.text)) 325 | } 326 | 327 | type unhandledGoto struct { 328 | line int 329 | label string 330 | resizePos int 331 | jumpPos int 332 | } 333 | 334 | type label struct { 335 | name string 336 | addr uint32 337 | nameTableSize uint32 338 | } 339 | 340 | var builtinFuncs = map[string]uint32{ 341 | "print": 0, 342 | "len": 1, 343 | "append": 2, 344 | "sub": 3, 345 | "type": 4, 346 | "delete": 5, 347 | "clone": 6, 348 | "__buffer_new": 7, 349 | "__buffer_readNumber": 8, 350 | "__buffer_writeNumber": 9, 351 | "__buffer_toString": 10, 352 | "__buffer_slice": 11, 353 | "__buffer_concat": 12, 354 | "__buffer_copy": 13, 355 | "__buffer_from": 14, 356 | "__open": 15, 357 | "__read": 16, 358 | "__write": 17, 359 | "__close": 18, 360 | "__seek": 19, 361 | "__remove": 20, 362 | "__fchmod": 21, 363 | "__chmod": 22, 364 | "__fchown": 23, 365 | "__chown": 24, 366 | "__fchdir": 25, 367 | "__chdir": 26, 368 | "__fstat": 27, 369 | "__stat": 28, 370 | "__rename": 29, 371 | "__mkdir": 30, 372 | "__exit": 31, 373 | "__getenv": 32, 374 | "__setenv": 33, 375 | "__readdir": 34, 376 | "__freaddir": 35, 377 | "throw": 36, 378 | "__args": 37, 379 | "__getegid": 38, 380 | "__geteuid": 39, 381 | "__getgid": 40, 382 | "__getpid": 41, 383 | "__getppid": 42, 384 | "__getuid": 43, 385 | "__exec": 44, 386 | } 387 | -------------------------------------------------------------------------------- /compiler/codegen/exp_gen.go: -------------------------------------------------------------------------------- 1 | package codegen 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "gscript/compiler/ast" 7 | "gscript/proto" 8 | "os" 9 | ) 10 | 11 | func genExps(exps []ast.Exp, ctx *Context, wantCnt int) { 12 | if len(exps) == 0 { 13 | for i := 0; i < wantCnt; i++ { 14 | ctx.insLoadNil() 15 | } 16 | return 17 | } 18 | last := len(exps) - 1 19 | for i := 0; i < last; i++ { 20 | genExp(exps[i], ctx, 1) 21 | } 22 | genExp(exps[last], ctx, wantCnt-len(exps)+1) 23 | } 24 | 25 | func genExp(exp ast.Exp, ctx *Context, retCnt int) { 26 | switch exp := exp.(type) { 27 | case *ast.NumberLiteralExp: 28 | genNumberLiteralExp(exp, ctx) 29 | retCnt-- 30 | case *ast.TrueExp: 31 | genTrueExp(ctx) 32 | retCnt-- 33 | case *ast.FalseExp: 34 | genFalseExp(ctx) 35 | retCnt-- 36 | case *ast.StringLiteralExp: 37 | genStringExp(exp, ctx) 38 | retCnt-- 39 | case *ast.NilExp: 40 | genNilExp(exp, ctx) 41 | retCnt-- 42 | case *ast.MapLiteralExp: 43 | genMapLiteralExp(exp, ctx) 44 | retCnt-- 45 | case *ast.ArrLiteralExp: 46 | genArrLiteralExp(exp, ctx) 47 | retCnt-- 48 | case *ast.FuncLiteralExp: 49 | genFuncLiteralExp(exp, ctx) 50 | retCnt-- 51 | case *ast.NewObjectExp: 52 | genNewObjectExp(exp, ctx) 53 | retCnt-- 54 | case *ast.NameExp: 55 | genNameExp(exp, ctx) 56 | retCnt-- 57 | case *ast.UnOpExp: 58 | genUnOpExp(exp, ctx) 59 | retCnt-- 60 | case *ast.BinOpExp: 61 | genBinOpExp(exp, ctx) 62 | retCnt-- 63 | case *ast.TernaryOpExp: 64 | genTernaryOpExp(exp, ctx) 65 | retCnt-- 66 | case *ast.FuncCallExp: 67 | genFuncCallExp(exp, ctx, retCnt) 68 | retCnt = 0 69 | default: 70 | fmt.Printf("[%s] unknown expression %v\n", curParsingFile, exp) 71 | os.Exit(0) 72 | } 73 | for i := 0; i < retCnt; i++ { 74 | ctx.insLoadNil() 75 | } 76 | } 77 | 78 | func genNewObjectExp(exp *ast.NewObjectExp, ctx *Context) { 79 | ctx.writeIns(proto.INS_NEW_EMPTY_MAP) 80 | genExps(exp.Args, ctx, len(exp.Args)) 81 | idx, ok := ctx.classes[exp.Name] 82 | if !ok { 83 | fmt.Printf("[%s:%d] undefined class '%s'\n", curParsingFile, exp.Line, exp.Name) 84 | os.Exit(0) 85 | } 86 | ctx.insLoadAnonymous(idx) 87 | ctx.insCall(0, byte(len(exp.Args))) 88 | } 89 | 90 | func genFuncLiteralExp(exp *ast.FuncLiteralExp, ctx *Context) { 91 | idx := uint32(len(ctx.ft.anonymousFuncs)) 92 | ctx.ft.anonymousFuncs = append(ctx.ft.anonymousFuncs, proto.AnonymousFuncProto{ 93 | Info: &proto.BasicInfo{ 94 | VaArgs: exp.VaArgs != "", 95 | Parameters: exp.Parameters, 96 | }, 97 | }) 98 | ctx.frame.nowParsingAnonymous = int(idx) 99 | ctx.insLoadAnonymous(idx) 100 | genFuncLiteral(&exp.FuncLiteral, ctx, idx, true) 101 | } 102 | 103 | func genFuncCallExp(exp *ast.FuncCallExp, ctx *Context, retCnt int) { 104 | genExps(exp.Args, ctx, len(exp.Args)) 105 | genExp(exp.Func, ctx, 1) 106 | ctx.insCall(byte(retCnt), byte(len(exp.Args))) 107 | } 108 | 109 | func genTernaryOpExp(exp *ast.TernaryOpExp, ctx *Context) { 110 | // TODO 111 | genExp(exp.Exp1, ctx, 1) 112 | ctx.writeIns(proto.INS_JUMP_IF) 113 | ctx.writeUint(0) // to determine steps to jump 114 | old := uint32(len(ctx.frame.text)) 115 | genExp(exp.Exp3, ctx, 1) 116 | ctx.writeIns(proto.INS_JUMP_REL) 117 | ctx.writeUint(0) // to determine steps to jump 118 | now := uint32(len(ctx.frame.text)) 119 | genExp(exp.Exp2, ctx, 1) 120 | last := uint32(len(ctx.frame.text)) 121 | 122 | // TODO 123 | binary.LittleEndian.PutUint32(ctx.frame.text[old-4:old], now-old) 124 | binary.LittleEndian.PutUint32(ctx.frame.text[now-4:now], last-now) 125 | } 126 | 127 | func genBinOpExp(exp *ast.BinOpExp, ctx *Context) { 128 | genExp(exp.Exp1, ctx, 1) 129 | switch exp.BinOp { 130 | case ast.BINOP_LAND: 131 | pos := ctx.insJumpIfLAnd(0) 132 | genExp(exp.Exp2, ctx, 1) 133 | ctx.setSteps(pos, ctx.textSize()) 134 | case ast.BINOP_LOR: 135 | pos := ctx.insJumpIfLOr(0) 136 | genExp(exp.Exp2, ctx, 1) 137 | ctx.setSteps(pos, ctx.textSize()) 138 | default: 139 | genExp(exp.Exp2, ctx, 1) 140 | ctx.writeIns(byte(exp.BinOp-ast.BINOP_START) + proto.INS_BINARY_START) 141 | } 142 | } 143 | 144 | func genUnOpExp(exp *ast.UnOpExp, ctx *Context) { 145 | switch exp.Op { 146 | case ast.UNOP_NOT: 147 | genExp(exp.Exp, ctx, 1) 148 | ctx.writeIns(proto.INS_UNARY_NOT) 149 | case ast.UNOP_LNOT: 150 | genExp(exp.Exp, ctx, 1) 151 | ctx.writeIns(proto.INS_UNARY_LNOT) 152 | case ast.UNOP_NEG: 153 | genExp(exp.Exp, ctx, 1) 154 | ctx.writeIns(proto.INS_UNARY_NEG) 155 | case ast.UNOP_DEC: // --i 156 | toAssignStmt(exp.Exp, ast.ASIGN_OP_SUBEQ, ctx) 157 | genExp(exp.Exp, ctx, 1) 158 | case ast.UNOP_INC: // ++i 159 | toAssignStmt(exp.Exp, ast.ASIGN_OP_ADDEQ, ctx) 160 | genExp(exp.Exp, ctx, 1) 161 | case ast.UNOP_DEC_: // i-- 162 | genExp(exp.Exp, ctx, 1) 163 | toAssignStmt(exp.Exp, ast.ASIGN_OP_SUBEQ, ctx) 164 | case ast.UNOP_INC_: // i++ 165 | genExp(exp.Exp, ctx, 1) 166 | toAssignStmt(exp.Exp, ast.ASIGN_OP_ADDEQ, ctx) 167 | } 168 | } 169 | 170 | func genNameExp(exp *ast.NameExp, ctx *Context) { 171 | ctx.insLoadName(exp.Name, exp.Line) 172 | } 173 | 174 | func genArrLiteralExp(exp *ast.ArrLiteralExp, ctx *Context) { 175 | for _, val := range exp.Vals { 176 | genExp(val, ctx, 1) 177 | } 178 | ctx.writeIns(proto.INS_SLICE_NEW) 179 | ctx.writeUint(uint32(len(exp.Vals))) 180 | } 181 | 182 | func genMapLiteralExp(exp *ast.MapLiteralExp, ctx *Context) { 183 | for i, key := range exp.Keys { 184 | ctx.insLoadConst(key) 185 | genExp(exp.Vals[i], ctx, 1) 186 | } 187 | ctx.writeIns(proto.INS_NEW_MAP) 188 | ctx.writeUint(uint32(len(exp.Keys))) 189 | } 190 | 191 | func genNilExp(exp *ast.NilExp, ctx *Context) { 192 | ctx.insLoadNil() 193 | } 194 | 195 | func genNumberLiteralExp(exp *ast.NumberLiteralExp, ctx *Context) { 196 | ctx.insLoadConst(exp.Value) 197 | } 198 | 199 | func genTrueExp(ctx *Context) { 200 | ctx.insLoadConst(true) 201 | } 202 | 203 | func genFalseExp(ctx *Context) { 204 | ctx.insLoadConst(false) 205 | } 206 | 207 | func genStringExp(exp *ast.StringLiteralExp, ctx *Context) { 208 | ctx.insLoadConst(exp.Value) 209 | } 210 | 211 | func toAssignStmt(exp ast.Exp, op int, ctx *Context) { 212 | stmt := &ast.VarAssignStmt{AssignOp: op, Rights: []ast.Exp{&ast.NumberLiteralExp{Value: int64(1)}}} 213 | if e, ok := exp.(*ast.NameExp); ok { 214 | stmt.Lefts = []ast.Var{{Prefix: e.Name}} 215 | genVarAssignStmt(stmt, ctx) 216 | return 217 | } 218 | var v ast.Var 219 | be := exp.(*ast.BinOpExp) 220 | for { 221 | v.Attrs = append(v.Attrs, be.Exp2) 222 | if e, ok := be.Exp1.(*ast.NameExp); ok { 223 | v.Prefix = e.Name 224 | break 225 | } 226 | be = be.Exp1.(*ast.BinOpExp) 227 | } 228 | for i := 0; i < len(v.Attrs)/2-1; i++ { 229 | mirror := len(v.Attrs) - i - 1 230 | v.Attrs[i], v.Attrs[mirror] = v.Attrs[mirror], v.Attrs[i] 231 | } 232 | stmt.Lefts = []ast.Var{v} 233 | genVarAssignStmt(stmt, ctx) 234 | } 235 | -------------------------------------------------------------------------------- /compiler/codegen/func_table.go: -------------------------------------------------------------------------------- 1 | package codegen 2 | 3 | import ( 4 | "gscript/compiler/ast" 5 | "gscript/proto" 6 | ) 7 | 8 | type FuncTable struct { 9 | funcTable []proto.FuncProto // index -> proto 10 | funcMap map[string]uint32 // funcname -> index 11 | 12 | anonymousFuncs []proto.AnonymousFuncProto 13 | } 14 | 15 | func newFuncTable(funcs []*ast.FuncDefStmt) *FuncTable { 16 | ft := &FuncTable{ 17 | funcMap: make(map[string]uint32), 18 | funcTable: make([]proto.FuncProto, len(funcs)), 19 | } 20 | for i, f := range funcs { 21 | ft.funcMap[f.Name] = uint32(i) 22 | info := new(proto.BasicInfo) 23 | info.Parameters = f.Parameters 24 | info.VaArgs = f.VaArgs != "" 25 | ft.funcTable[i].Info = info 26 | } 27 | return ft 28 | } 29 | -------------------------------------------------------------------------------- /compiler/codegen/name_table.go: -------------------------------------------------------------------------------- 1 | package codegen 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | ) 7 | 8 | type NameTable struct { 9 | nameTable map[string]variable 10 | nameIdx *uint32 11 | prev *NameTable 12 | } 13 | 14 | type variable struct { 15 | idx uint32 16 | line uint32 17 | } 18 | 19 | func NewNameTable() *NameTable { 20 | return newNameTable(new(uint32)) 21 | } 22 | 23 | func newNameTable(nameIdx *uint32) *NameTable { 24 | return &NameTable{ 25 | nameTable: make(map[string]variable), 26 | nameIdx: nameIdx, 27 | } 28 | } 29 | 30 | func (nt *NameTable) Set(name string, line uint32) { 31 | if v, ok := nt.nameTable[name]; ok { 32 | fmt.Printf("[%s] variable '%s' already declared at line %d, but redecalres at line %d", curParsingFile, name, v.line, line) 33 | os.Exit(0) 34 | } 35 | nt.nameTable[name] = variable{ 36 | idx: *nt.nameIdx, 37 | line: line, 38 | } 39 | *nt.nameIdx++ 40 | } 41 | 42 | func (nt *NameTable) get(name string) (uint32, bool) { 43 | for t := nt; t != nil; t = t.prev { 44 | if v, ok := t.nameTable[name]; ok { 45 | return v.idx, true 46 | } 47 | } 48 | return 0, false 49 | } 50 | -------------------------------------------------------------------------------- /compiler/codegen/stack.go: -------------------------------------------------------------------------------- 1 | package codegen 2 | 3 | type forBlock struct { 4 | nameCnt uint32 5 | curTryLevel int 6 | prev interface{} 7 | breaks []int 8 | continues []int 9 | } 10 | 11 | type switchBlock struct { 12 | nameCnt uint32 13 | curTryLevel int 14 | prev interface{} 15 | breaks []int 16 | _fallthrough *int 17 | } 18 | 19 | type blockStack struct { 20 | cur interface{} 21 | } 22 | 23 | func newBlockStack() *blockStack { 24 | return &blockStack{} 25 | } 26 | 27 | func (bs *blockStack) pop() { 28 | if b, ok := bs.cur.(*forBlock); ok { 29 | bs.cur = b.prev 30 | return 31 | } 32 | if b, ok := bs.cur.(*switchBlock); ok { 33 | bs.cur = b.prev 34 | return 35 | } 36 | } 37 | 38 | func (bs *blockStack) pushSwitch(nameCnt uint32, curTryLevel int) { 39 | b := &switchBlock{nameCnt: nameCnt, prev: bs.cur, curTryLevel: curTryLevel} 40 | bs.cur = b 41 | } 42 | 43 | func (bs *blockStack) pushFor(nameCnt uint32, curTryLevel int) { 44 | b := &forBlock{nameCnt: nameCnt, prev: bs.cur, curTryLevel: curTryLevel} 45 | bs.cur = b 46 | } 47 | 48 | func (bs *blockStack) latestFor() *forBlock { 49 | cur := bs.cur 50 | for cur != nil { 51 | if fb, ok := cur.(*forBlock); ok { 52 | return fb 53 | } 54 | cur = cur.(*switchBlock).prev 55 | } 56 | return nil 57 | } 58 | 59 | func (bs *blockStack) latestSwitch() *switchBlock { 60 | cur := bs.cur 61 | for cur != nil { 62 | if fb, ok := cur.(*switchBlock); ok { 63 | return fb 64 | } 65 | cur = cur.(*forBlock).prev 66 | } 67 | return nil 68 | } 69 | 70 | func (bs *blockStack) top() interface{} { 71 | return bs.cur 72 | } 73 | -------------------------------------------------------------------------------- /compiler/codegen/stack_frame.go: -------------------------------------------------------------------------------- 1 | package codegen 2 | 3 | type StackFrame struct { 4 | prev *StackFrame 5 | nt *NameTable 6 | vt *UpValueTable 7 | bs *blockStack 8 | validLabels map[string]label 9 | returnAtEnd bool 10 | text []byte 11 | 12 | nowParsingAnonymous int 13 | curTryLevel int 14 | } 15 | 16 | func newStackFrame() *StackFrame { 17 | return &StackFrame{ 18 | nt: NewNameTable(), 19 | bs: newBlockStack(), 20 | validLabels: map[string]label{}, 21 | vt: newUpValueTable(), 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /compiler/codegen/upvalue_table.go: -------------------------------------------------------------------------------- 1 | package codegen 2 | 3 | type UpValue struct { 4 | level uint32 5 | nameIdx uint32 6 | name string 7 | } 8 | 9 | type UpValueTable struct { 10 | upValues []UpValue 11 | } 12 | 13 | func newUpValueTable() *UpValueTable { 14 | return &UpValueTable{} 15 | } 16 | 17 | func (vt *UpValueTable) get(name string) (upValueIdx uint32, level uint32, ok bool) { 18 | for i, v := range vt.upValues { 19 | if v.name == name { 20 | return uint32(i), v.level, true 21 | } 22 | } 23 | return 0, 0, false 24 | } 25 | 26 | func (vt *UpValueTable) set(name string, level uint32, nameIdx uint32) (upValueIdx uint32) { 27 | vt.upValues = append(vt.upValues, UpValue{level, nameIdx, name}) 28 | return uint32(len(vt.upValues) - 1) 29 | } 30 | -------------------------------------------------------------------------------- /compiler/complie.go: -------------------------------------------------------------------------------- 1 | package compiler 2 | 3 | import ( 4 | "fmt" 5 | "gscript/compiler/codegen" 6 | "gscript/compiler/lexer" 7 | "gscript/compiler/parser" 8 | "gscript/proto" 9 | "gscript/std" 10 | "io/ioutil" 11 | "os" 12 | "path" 13 | ) 14 | 15 | const maxLimitFileSize = 10 << 20 // 10MB 16 | 17 | func readCode(path string) ([]byte, error) { 18 | info, err := os.Stat(path) 19 | if err != nil { 20 | if os.IsNotExist(err) { 21 | return nil, fmt.Errorf("%s: imported file is not exists", path) 22 | } 23 | return nil, err 24 | } 25 | if info.Size() > maxLimitFileSize { 26 | return nil, fmt.Errorf("%s: file size exceeds 10MB limit", path) 27 | } 28 | return ioutil.ReadFile(path) 29 | } 30 | 31 | func ComplieWithSrcFile(path string) (protos []proto.Proto, err error) { 32 | code, err := readCode(path) 33 | if err != nil { 34 | return 35 | } 36 | return ComplieWithSrcCode(code, path) 37 | } 38 | 39 | func ComplieWithSrcCode(code []byte, filename string) (protos []proto.Proto, err error) { 40 | graph := newGraph() 41 | n := graph.insert(filename) 42 | if err = complie(code, n, graph); err != nil { 43 | return 44 | } 45 | if graph.hasCircle() { 46 | return nil, fmt.Errorf("import circle occurs") 47 | } 48 | return graph.sortProtos(), nil 49 | } 50 | 51 | func complie(code []byte, n *node, graph *graph) error { 52 | parser := parser.NewParser(lexer.NewLexer(n.pathname, code)) 53 | prog := parser.Parse() 54 | 55 | var imports []codegen.Import 56 | var nodes []*node 57 | for _, _import := range prog.Imports { 58 | for _, lib := range _import.Libs { 59 | var protoNumber uint32 60 | if lib.Stdlib { 61 | var err error 62 | protoNumber, err = std.GetLibProtoNumByName(lib.Path) 63 | if err != nil { 64 | return err 65 | } 66 | } else { 67 | nn := graph.insertPath(n.pathname, lib.Path+".gs") 68 | nodes = append(nodes, nn) 69 | protoNumber = nn.protoNum 70 | } 71 | alias := lib.Alias 72 | if alias == "" { 73 | alias = path.Base(lib.Path) 74 | } 75 | imports = append(imports, codegen.Import{ 76 | Line: uint32(_import.Line), 77 | ProtoNumber: protoNumber, 78 | Alias: alias, 79 | StdLib: lib.Stdlib, 80 | }) 81 | } 82 | } 83 | 84 | proto := codegen.Gen(parser, prog, imports, n.protoNum) 85 | proto.FilePath = n.pathname 86 | n.proto = &proto 87 | 88 | for _, n = range nodes { 89 | // if file has been already complied 90 | if n.proto != nil { 91 | continue 92 | } 93 | code, err := readCode(n.pathname) 94 | if err != nil { 95 | return err 96 | } 97 | if err := complie(code, n, graph); err != nil { 98 | return err 99 | } 100 | } 101 | return nil 102 | } 103 | -------------------------------------------------------------------------------- /compiler/graph.go: -------------------------------------------------------------------------------- 1 | package compiler 2 | 3 | import ( 4 | "fmt" 5 | "gscript/proto" 6 | "os" 7 | "path/filepath" 8 | ) 9 | 10 | const ( 11 | white = iota 12 | grey 13 | black 14 | ) 15 | 16 | type node struct { 17 | color uint32 18 | protoNum uint32 19 | proto *proto.Proto 20 | pathname string 21 | imports []*node 22 | } 23 | 24 | type graph struct { 25 | nodes map[string]*node 26 | } 27 | 28 | func newGraph() *graph { 29 | return &graph{ 30 | nodes: make(map[string]*node), 31 | } 32 | } 33 | 34 | func abs(path string) string { 35 | path, err := filepath.Abs(path) 36 | if err != nil { 37 | fmt.Printf("get absolute filepath of '%s' failed\n", path) 38 | fmt.Printf("\terror message: %v\n", err) 39 | os.Exit(0) 40 | } 41 | return path 42 | } 43 | 44 | func (g *graph) sortProtos() []proto.Proto { 45 | protos := make([]proto.Proto, len(g.nodes)) 46 | for _, node := range g.nodes { 47 | protos[node.protoNum] = *node.proto 48 | } 49 | return protos 50 | } 51 | 52 | func (g *graph) hasCircle() bool { 53 | var n *node 54 | for _, n = range g.nodes { 55 | if n.color != white { 56 | continue 57 | } 58 | if hasCircle(n) { 59 | return true 60 | } 61 | } 62 | return false 63 | } 64 | 65 | func hasCircle(n *node) bool { 66 | n.color = grey 67 | for _, _import := range n.imports { 68 | if _import.color == grey { 69 | return true 70 | } 71 | if hasCircle(_import) { 72 | return true 73 | } 74 | } 75 | n.color = black 76 | return false 77 | } 78 | 79 | func chdir(dir string) { 80 | err := os.Chdir(dir) 81 | if err == nil { 82 | return 83 | } 84 | fmt.Printf("change work dir failed\n") 85 | fmt.Printf("\terror message: %v\n", err) 86 | os.Exit(0) 87 | } 88 | 89 | func getImportPath(base string, _import string) string { 90 | curDir := abs(".") 91 | chdir(filepath.Dir(base)) 92 | res := abs(_import) 93 | chdir(curDir) 94 | return res 95 | } 96 | 97 | func (g *graph) insertPath(from, to string) *node { 98 | from = abs(from) 99 | to = getImportPath(from, to) 100 | n := g.nodes[from] 101 | nn, ok := g.nodes[to] 102 | if !ok { 103 | nn = &node{pathname: to, protoNum: uint32(len(g.nodes))} 104 | g.nodes[to] = nn 105 | } 106 | n.imports = append(n.imports, nn) 107 | return nn 108 | } 109 | 110 | func (g *graph) insert(path string) *node { 111 | path = abs(path) 112 | n := &node{pathname: path, protoNum: uint32(len(g.nodes))} 113 | g.nodes[path] = n 114 | return n 115 | } 116 | -------------------------------------------------------------------------------- /compiler/lexer/lexer.go: -------------------------------------------------------------------------------- 1 | package lexer 2 | 3 | import ( 4 | "fmt" 5 | "gscript/compiler/token" 6 | "os" 7 | "strconv" 8 | "strings" 9 | ) 10 | 11 | const ( 12 | CHAR_EOF byte = 0 13 | CHAR_CR byte = '\r' 14 | CHAR_LF byte = '\n' 15 | CHAR_SPACE byte = ' ' 16 | CHAR_TAB byte = '\t' 17 | ) 18 | 19 | type Lexer struct { 20 | line int // current line number 21 | column int // column of current charactor in current line 22 | cursor int // number of next character needs to be parse 23 | src []byte // source code 24 | srcFile string // source file path 25 | curToken *token.Token // current token 26 | aheadToken *token.Token // save LookAhead token temporarily 27 | } 28 | 29 | func NewLexer(srcFile string, src []byte) *Lexer { 30 | return &Lexer{ 31 | line: 1, 32 | src: src, 33 | cursor: 0, 34 | srcFile: srcFile, 35 | } 36 | } 37 | 38 | func (l *Lexer) Line() int { 39 | return l.line 40 | } 41 | 42 | func (l *Lexer) Column() int { 43 | return l.column 44 | } 45 | 46 | func (l *Lexer) SrcFile() string { 47 | return l.srcFile 48 | } 49 | 50 | // Look ahead 1 token 51 | func (l *Lexer) LookAhead() (token *token.Token) { 52 | if l.aheadToken != nil { 53 | return l.aheadToken 54 | } 55 | l.aheadToken = l.nextToken() 56 | return l.aheadToken 57 | } 58 | 59 | func (l *Lexer) NextToken() (token *token.Token) { 60 | // if LookAhead before 61 | if l.aheadToken != nil { 62 | token, l.aheadToken = l.aheadToken, nil 63 | return 64 | } 65 | return l.nextToken() 66 | } 67 | 68 | func (l *Lexer) nextToken() *token.Token { 69 | again: 70 | if breakLine := l.skipWhiteSpace(); breakLine && l.needAddSemi() { 71 | l.genSemiToken() 72 | return l.curToken 73 | } 74 | if l.reachEndOfFile() { 75 | return token.EOFToken 76 | } 77 | curCh := l.src[l.cursor] 78 | switch curCh { 79 | case '.': 80 | nextCh, gapCh := l.lookAhead(1), l.lookAhead(2) 81 | if nextCh == '.' && gapCh == '.' { 82 | l.genToken(token.TOKEN_SEP_VARARG, 3) 83 | l.forward(2) 84 | break 85 | } 86 | l.genToken(token.TOKEN_SEP_DOT, 1) 87 | case '"', '\'': 88 | l.scanStringLiteral() 89 | case ':': 90 | l.genToken(token.TOKEN_SEP_COLON, 1) 91 | case ';': 92 | l.genToken(token.TOKEN_SEP_SEMI, 1) 93 | case ',': 94 | l.genToken(token.TOKEN_SEP_COMMA, 1) 95 | case '?': 96 | l.genToken(token.TOKEN_SEP_QMARK, 1) 97 | case '[': 98 | l.genToken(token.TOKEN_SEP_LBRACK, 1) 99 | case ']': 100 | l.genToken(token.TOKEN_SEP_RBRACK, 1) 101 | case '(': 102 | l.genToken(token.TOKEN_SEP_LPAREN, 1) 103 | case ')': 104 | l.genToken(token.TOKEN_SEP_RPAREN, 1) 105 | case '{': 106 | l.genToken(token.TOKEN_SEP_LCURLY, 1) 107 | case '}': 108 | l.genToken(token.TOKEN_SEP_RCURLY, 1) 109 | case '+': 110 | nextCh := l.lookAhead(1) 111 | if nextCh == '=' { 112 | l.genToken(token.TOKEN_OP_ADDEQ, 2) 113 | l.forward(1) 114 | } else if nextCh == '+' { 115 | l.genToken(token.TOKEN_OP_INC, 2) 116 | l.forward(1) 117 | } else { 118 | l.genToken(token.TOKEN_OP_ADD, 1) 119 | } 120 | case '-': 121 | nextCh := l.lookAhead(1) 122 | if nextCh == '=' { 123 | l.genToken(token.TOKEN_OP_SUBEQ, 2) 124 | l.forward(1) 125 | } else if nextCh == '-' { 126 | l.genToken(token.TOKEN_OP_DEC, 2) 127 | l.forward(1) 128 | } else { 129 | l.genToken(token.TOKEN_OP_SUB, 1) 130 | } 131 | case '*': 132 | nextCh := l.lookAhead(1) 133 | if nextCh == '=' { 134 | l.genToken(token.TOKEN_OP_MULEQ, 2) 135 | l.forward(1) 136 | break 137 | } 138 | l.genToken(token.TOKEN_OP_MUL, 1) 139 | case '/': 140 | nextCh := l.lookAhead(1) 141 | if nextCh == '=' { 142 | l.genToken(token.TOKEN_OP_DIVEQ, 2) 143 | l.forward(1) 144 | } else if nextCh == '/' { 145 | l.genToken(token.TOKEN_OP_IDIV, 2) 146 | l.forward(1) 147 | } else { 148 | l.genToken(token.TOKEN_OP_DIV, 1) 149 | } 150 | case '%': 151 | nextCh := l.lookAhead(1) 152 | if nextCh == '=' { 153 | l.genToken(token.TOKEN_OP_MODEQ, 2) 154 | l.forward(1) 155 | break 156 | } 157 | l.genToken(token.TOKEN_OP_MOD, 1) 158 | case '&': 159 | nextCh := l.lookAhead(1) 160 | if nextCh == '=' { 161 | l.genToken(token.TOKEN_OP_ANDEQ, 2) 162 | l.forward(1) 163 | } else if nextCh == '&' { 164 | l.genToken(token.TOKEN_OP_LAND, 2) 165 | l.forward(1) 166 | } else { 167 | l.genToken(token.TOKEN_OP_AND, 1) 168 | } 169 | case '|': 170 | nextCh := l.lookAhead(1) 171 | if nextCh == '=' { 172 | l.genToken(token.TOKEN_OP_OREQ, 2) 173 | l.forward(1) 174 | } else if nextCh == '|' { 175 | l.genToken(token.TOKEN_OP_LOR, 2) 176 | l.forward(1) 177 | } else { 178 | l.genToken(token.TOKEN_OP_OR, 1) 179 | } 180 | case '^': 181 | nextCh := l.lookAhead(1) 182 | if nextCh == '=' { 183 | l.genToken(token.TOKEN_OP_XOREQ, 2) 184 | l.forward(1) 185 | break 186 | } 187 | l.genToken(token.TOKEN_OP_XOR, 1) 188 | case '~': 189 | l.genToken(token.TOKEN_OP_NOT, 1) 190 | case '=': 191 | nextCh := l.lookAhead(1) 192 | if nextCh == '=' { 193 | l.genToken(token.TOKEN_OP_EQ, 2) 194 | l.forward(1) 195 | break 196 | } 197 | l.genToken(token.TOKEN_OP_ASSIGN, 1) 198 | case '!': 199 | nextCh := l.lookAhead(1) 200 | if nextCh == '=' { 201 | l.genToken(token.TOKEN_OP_NE, 2) 202 | l.forward(1) 203 | break 204 | } 205 | l.genToken(token.TOKEN_OP_LNOT, 1) 206 | case '<': 207 | nextCh := l.lookAhead(1) 208 | if nextCh == '=' { 209 | l.genToken(token.TOKEN_OP_LE, 2) 210 | l.forward(1) 211 | } else if nextCh == '<' { 212 | l.genToken(token.TOKEN_OP_SHL, 2) 213 | l.forward(1) 214 | } else { 215 | l.genToken(token.TOKEN_OP_LT, 1) 216 | } 217 | case '>': 218 | nextCh := l.lookAhead(1) 219 | if nextCh == '=' { 220 | l.genToken(token.TOKEN_OP_GE, 2) 221 | l.forward(1) 222 | } else if nextCh == '>' { 223 | l.genToken(token.TOKEN_OP_SHR, 2) 224 | l.forward(1) 225 | } else { 226 | l.genToken(token.TOKEN_OP_GT, 1) 227 | } 228 | case '#': // comment 229 | l.skipComment() 230 | goto again 231 | default: 232 | if isDigit(curCh) { 233 | l.scanNumber() 234 | } else if isLetter_(curCh) { 235 | l.scanIdentifier() 236 | } else { 237 | l.error("unexpected symbol near '%c'", curCh) 238 | } 239 | } 240 | 241 | l.forward(1) 242 | return l.curToken 243 | } 244 | 245 | func (l *Lexer) scanIdentifier() { 246 | k := l.cursor + 1 247 | for ; k < len(l.src) && (isLetter_(l.src[k]) || isDigit(l.src[k])); k++ { 248 | } 249 | 250 | l.genToken(token.TOKEN_IDENTIFIER, k-l.cursor) 251 | id := string(l.src[l.cursor:k]) 252 | l.curToken.Value = id 253 | if kind, ok := token.Keywords[id]; ok { 254 | l.curToken.Kind = kind 255 | } 256 | l.forward(k - l.cursor - 1) 257 | } 258 | 259 | // dec: 1234, hex: 0xFF, oct: 017, float: 0.123 260 | func (l *Lexer) scanNumber() { 261 | var num, base int64 262 | var value interface{} 263 | 264 | k := l.cursor 265 | firstDigit := l.src[k] 266 | if firstDigit != '0' { // dec 267 | base = 10 268 | } else { 269 | nextCh := l.lookAhead(1) 270 | if nextCh == '.' { // float 271 | k, value = l.scanFloat(k + 2) 272 | goto end 273 | } 274 | if isDigit(nextCh) { // oct 275 | base = 8 276 | k += 1 277 | } else if nextCh == 'x' { // hex 278 | if gapCh := l.lookAhead(2); gapCh == CHAR_EOF || !isHexDigit(gapCh) { 279 | l.error("invalid hex number near '%c'", firstDigit) 280 | } 281 | base = 16 282 | k += 2 283 | } else { // 0 284 | base = 10 285 | } 286 | } 287 | 288 | for ; k < len(l.src) && 289 | (l.src[k] == '.' || 290 | base == 8 && l.src[k] < '8' && l.src[k] >= '0' || 291 | base == 10 && isDigit(l.src[k]) || 292 | base == 16 && isHexDigit(l.src[k])); k++ { 293 | if l.src[k] == '.' { 294 | k, value = l.scanFloat(k + 1) 295 | goto end 296 | } 297 | num = num*base + toNumber(l.src[k]) 298 | } 299 | value = num 300 | end: 301 | contentLength := k - l.cursor 302 | l.genToken(token.TOKEN_NUMBER, contentLength) 303 | l.curToken.Value = value 304 | l.forward(contentLength - 1) 305 | } 306 | 307 | func (l *Lexer) scanFloat(start int) (end int, value float64) { 308 | for end = start; end < len(l.src) && isDigit(l.src[end]); end++ { 309 | } 310 | value, err := strconv.ParseFloat(string(l.src[l.cursor:end]), 64) 311 | if err != nil { 312 | l.error(err.Error()) 313 | } 314 | return end, value 315 | } 316 | 317 | func (l *Lexer) scanStringLiteral() { 318 | curCh := l.src[l.cursor] 319 | k := 1 320 | var b strings.Builder 321 | escape := false 322 | start := l.cursor + 1 // skip first " or ' 323 | for { 324 | ahead := l.lookAhead(k) 325 | if ahead == CHAR_EOF || ahead == CHAR_CR || ahead == CHAR_LF { 326 | l.error("expect another quotation mark before end of file or newline") 327 | } 328 | if isQuoteAndUnmatched(ahead, curCh) { 329 | l.error("expect another %c, but got %c", curCh, ahead) 330 | } 331 | if ahead == l.src[l.cursor] { //matched 332 | break 333 | } 334 | if ahead != '\\' { 335 | k++ 336 | continue 337 | } 338 | gap := l.lookAhead(k + 1) 339 | if gap == CHAR_EOF { 340 | l.error("expect another quotation mark before end of file or newline") 341 | } 342 | // only support \n, \t, \', \", \\, 343 | if gap != 'n' && gap != 't' && gap != '\'' && gap != '"' && gap != '\\' { 344 | l.cursor += k 345 | l.error("invalid escape character \\%c", gap) 346 | } 347 | escape = true 348 | b.Write(l.src[start : l.cursor+k]) 349 | if gap == 'n' { 350 | b.WriteByte('\n') 351 | } else if gap == 't' { 352 | b.WriteByte('\t') 353 | } else { 354 | b.WriteByte(gap) 355 | } 356 | k += 2 357 | start = l.cursor + k 358 | } 359 | l.genToken(token.TOKEN_STRING, k+1) 360 | if escape { 361 | b.Write(l.src[start : l.cursor+k]) 362 | l.curToken.Value = b.String() 363 | } else { 364 | l.curToken.Value = string(l.src[l.cursor+1 : l.cursor+k]) 365 | } 366 | l.forward(k) 367 | } 368 | 369 | func (l *Lexer) skipComment() { 370 | for k := 1; ; k++ { 371 | ahead := l.lookAhead(k) 372 | if ahead == CHAR_EOF || ahead == CHAR_CR || ahead == CHAR_LF { 373 | l.forward(k) 374 | break 375 | } 376 | } 377 | } 378 | 379 | // skip \r, \n, \r\n, \t, space 380 | func (l *Lexer) skipWhiteSpace() (breakLine bool) { 381 | for l.cursor < len(l.src) { 382 | switch l.src[l.cursor] { 383 | case CHAR_CR: 384 | breakLine = true 385 | nextCh := l.lookAhead(1) 386 | if nextCh == CHAR_EOF { 387 | break 388 | } 389 | if nextCh != CHAR_LF { 390 | l.column = 0 391 | l.line++ 392 | break 393 | } 394 | l.cursor++ 395 | fallthrough 396 | case CHAR_LF: 397 | breakLine = true 398 | l.column = 0 399 | l.line++ 400 | case CHAR_SPACE: 401 | l.column++ 402 | case CHAR_TAB: 403 | l.column++ 404 | default: 405 | return 406 | } 407 | l.cursor++ 408 | } 409 | // add breakLine at last of code so that we can tell 410 | // if need add semicolon for the last statement 411 | if l.cursor == len(l.src) { 412 | return true 413 | } 414 | return 415 | } 416 | 417 | // lookAhead @k characters 418 | func (l *Lexer) lookAhead(k int) byte { 419 | idx := l.cursor + k 420 | if idx >= len(l.src) { 421 | return CHAR_EOF 422 | } 423 | return l.src[idx] 424 | } 425 | 426 | func (l *Lexer) error(format string, args ...interface{}) { 427 | errMsg := fmt.Sprintf(format, args...) 428 | fmt.Printf("Lexer error: [%s:%d:%d]\n", l.srcFile, l.line, l.column+1) 429 | fmt.Printf("\t%s\n", errMsg) 430 | os.Exit(0) 431 | } 432 | 433 | // move cursor and kth @k steps 434 | func (l *Lexer) forward(k int) { 435 | l.cursor += k 436 | l.column += k 437 | } 438 | 439 | func (l *Lexer) reachEndOfFile() bool { 440 | return l.cursor >= len(l.src) 441 | } 442 | 443 | func (l *Lexer) genToken(kind, contentLength int) { 444 | l.curToken = &token.Token{ 445 | Kind: kind, 446 | Line: l.line, 447 | Kth: l.column, 448 | Content: string(l.src[l.cursor : l.cursor+contentLength]), 449 | } 450 | } 451 | 452 | func isQuoteAndUnmatched(a, b byte) bool { 453 | return (a == '"' || a == '\'') && a != b 454 | } 455 | 456 | func isDigit(ch byte) bool { 457 | return ch <= '9' && ch >= '0' 458 | } 459 | 460 | func isHexDigit(ch byte) bool { 461 | return ch <= '9' && ch >= '0' || ch <= 'f' && ch >= 'a' || ch <= 'F' && ch >= 'A' 462 | } 463 | 464 | func isLetter_(ch byte) bool { 465 | return ch == '_' || ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' 466 | } 467 | 468 | func toNumber(digit byte) (result int64) { 469 | if !isDigit(digit) { 470 | result += 9 471 | } 472 | return result + int64(digit&15) 473 | } 474 | 475 | var addSemiTokens = map[int]struct{}{ 476 | token.TOKEN_IDENTIFIER: {}, 477 | token.TOKEN_NUMBER: {}, 478 | token.TOKEN_STRING: {}, 479 | token.TOKEN_KW_BREAK: {}, 480 | token.TOKEN_KW_FALLTHROUGH: {}, 481 | token.TOKEN_KW_CONTINUE: {}, 482 | token.TOKEN_KW_RETURN: {}, 483 | token.TOKEN_OP_INC: {}, 484 | token.TOKEN_OP_DEC: {}, 485 | token.TOKEN_SEP_RBRACK: {}, 486 | token.TOKEN_SEP_RCURLY: {}, 487 | token.TOKEN_SEP_RPAREN: {}, 488 | token.TOKEN_KW_NIL: {}, 489 | token.TOKEN_KW_TRUE: {}, 490 | token.TOKEN_KW_FALSE: {}, 491 | } 492 | 493 | func needAddSemi(kind int) bool { 494 | _, ok := addSemiTokens[kind] 495 | return ok 496 | } 497 | 498 | func (l *Lexer) needAddSemi() bool { 499 | return l.curToken != nil && needAddSemi(l.curToken.Kind) 500 | } 501 | 502 | func (l *Lexer) genSemiToken() { 503 | l.curToken = &token.Token{ 504 | Kind: token.TOKEN_SEP_SEMI, 505 | Line: l.curToken.Line, 506 | Kth: l.curToken.Kth + len(l.curToken.Content), 507 | Content: ";", 508 | } 509 | } 510 | -------------------------------------------------------------------------------- /compiler/lexer/lexer_test.go: -------------------------------------------------------------------------------- 1 | package lexer 2 | 3 | import ( 4 | "fmt" 5 | . "gscript/compiler/token" 6 | "math/rand" 7 | "strings" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | func init() { 13 | rand.Seed(int64(time.Now().Unix())) 14 | } 15 | 16 | func Test_IDENTIFIER_Token(t *testing.T) { 17 | const times = 50 18 | for i := 0; i < times; i++ { 19 | tl := &TokenList{line: 1} 20 | for j := 0; j < 50; j++ { 21 | for content := range IDENTIFIER_TOKEN_MAP { 22 | for roll, m := rand.Int()%3+1, 0; m < roll; m++ { 23 | tl.newToken(TOKEN_IDENTIFIER, content, content) 24 | } 25 | } 26 | for kw, kind := range Keywords { 27 | for roll, m := rand.Int()%3+1, 0; m < roll; m++ { 28 | tl.newToken(kind, kw, kw) 29 | } 30 | } 31 | } 32 | match(tl.getTokens(), NewLexer("Test_IDENTIFIER_Token", []byte(tl.srcCode.String())), t) 33 | } 34 | } 35 | 36 | func Test_STRING_Token(t *testing.T) { 37 | const times = 50 38 | for i := 0; i < times; i++ { 39 | tl := &TokenList{line: 1} 40 | for j := 0; j < 50; j++ { 41 | for content, value := range STRING_TOKEN_MAP { 42 | for roll, m := rand.Int()%3+1, 0; m < roll; m++ { 43 | tl.newToken(TOKEN_STRING, content, value) 44 | } 45 | } 46 | } 47 | match(tl.getTokens(), NewLexer("Test_STRING_Token", []byte(tl.srcCode.String())), t) 48 | } 49 | } 50 | 51 | func Test_NUMBER_Token(t *testing.T) { 52 | const times = 50 53 | for i := 0; i < times; i++ { 54 | tl := &TokenList{line: 1} 55 | for j := 0; j < 50; j++ { 56 | for content, value := range NUMBER_TOKEN_MAP { 57 | for roll, m := rand.Int()%3+1, 0; m < roll; m++ { 58 | tl.newToken(TOKEN_NUMBER, content, value) 59 | } 60 | } 61 | } 62 | match(tl.getTokens(), NewLexer("Test_NUMBER_Token", []byte(tl.srcCode.String())), t) 63 | } 64 | } 65 | 66 | func Test_OP_SEP_Token(t *testing.T) { 67 | const times = 50 68 | for i := 0; i < times; i++ { 69 | tl := &TokenList{line: 1} 70 | for j := 0; j < 50; j++ { 71 | for kind, content := range OP_SEP_TOKEN_MAP { 72 | for roll, m := rand.Int()%3+1, 0; m < roll; m++ { 73 | tl.newToken(kind, content, nil) 74 | } 75 | } 76 | } 77 | match(tl.getTokens(), NewLexer("Test_OP_Token", []byte(tl.srcCode.String())), t) 78 | } 79 | } 80 | 81 | type TokenList struct { 82 | tokens []*Token 83 | srcCode strings.Builder 84 | line, kth int 85 | } 86 | 87 | func (tl *TokenList) getTokens() []*Token { 88 | if tl.tokens == nil { 89 | return nil 90 | } 91 | lastToken := tl.tokens[len(tl.tokens)-1] 92 | if lastToken.Line == tl.line && needAddSemi(lastToken.Kind) { 93 | tl.tokens = append(tl.tokens, &Token{ 94 | Kind: TOKEN_SEP_SEMI, 95 | Content: ";", 96 | Line: lastToken.Line, 97 | Kth: lastToken.Kth + len(lastToken.Content), 98 | }) 99 | } 100 | return tl.tokens 101 | } 102 | 103 | func (tl *TokenList) newToken(kind int, content string, value interface{}) { 104 | tl.tokens = append(tl.tokens, &Token{ 105 | Kind: kind, 106 | Line: tl.line, 107 | Kth: tl.kth, 108 | Content: content, 109 | Value: value, 110 | }) 111 | tl.srcCode.WriteString(content) 112 | tl.kth += len(content) 113 | // try to write 1+ whiteSpace into source code 114 | tl.writeWhiteSpace() 115 | } 116 | 117 | func (tl *TokenList) writeWhiteSpace() { 118 | random := rand.Int() % 8 119 | if random == 0 { // 1/8 probability of writing break line 120 | tl.writeCRLF() 121 | } else { // 7/8 probability of writing '\t' or ' ' 122 | tl.writeTabOrSpace(random%3 + 1) 123 | } 124 | } 125 | 126 | // write k '\t' or ' ' into srcCode 127 | func (tl *TokenList) writeTabOrSpace(k int) { 128 | for i := 0; i < k; i++ { 129 | if rand.Int()%2 == 0 { 130 | tl.srcCode.WriteByte('\t') 131 | } else { 132 | tl.srcCode.WriteByte(' ') 133 | } 134 | tl.kth++ 135 | } 136 | } 137 | 138 | // write random break lines into srcCode 139 | func (tl *TokenList) writeCRLF() { 140 | base := 1 141 | for { 142 | roll := rand.Int() % base 143 | if roll != 0 { 144 | break 145 | } 146 | if tl.tokens != nil { 147 | latest := tl.tokens[len(tl.tokens)-1] 148 | if needAddSemi(latest.Kind) { 149 | tl.tokens = append(tl.tokens, &Token{ 150 | Kind: TOKEN_SEP_SEMI, 151 | Line: latest.Line, 152 | Kth: latest.Kth + len(latest.Content), 153 | Content: ";", 154 | }) 155 | } 156 | } 157 | tl.line++ 158 | tl.kth = 0 159 | // 1/base probability of writing break line 160 | if rand.Int()%2 == 0 { 161 | tl.srcCode.Write([]byte("\r\n")) 162 | } else { 163 | tl.srcCode.WriteByte('\n') 164 | } 165 | base *= 2 166 | } 167 | } 168 | 169 | func match(tokens []*Token, l *Lexer, t *testing.T) { 170 | for _, want := range tokens { 171 | got := l.NextToken() 172 | gotV, wantV := tokenValue(got), tokenValue(want) 173 | if gotV != wantV { 174 | t.Errorf("source code:\n %s\n", l.src) 175 | t.Fatalf("want token %s, but got %s\n", wantV, gotV) 176 | } 177 | if got.Content != want.Content { 178 | // t.Errorf("source code:\n %s\n", l.src) 179 | t.Fatalf("want content %s, but got %s\n", want.Content, got.Content) 180 | } 181 | } 182 | lastToken := l.NextToken() 183 | if lastToken.Kind != TOKEN_EOF { 184 | t.Fatalf("should reach EOF, but got one more token %s", tokenValue(lastToken)) 185 | } 186 | } 187 | 188 | func tokenValue(token *Token) string { 189 | var tokenVal string 190 | if token.Kind == TOKEN_EOF { 191 | tokenVal = "" 192 | } else if token.Kind >= TOKEN_SEP_DOT && token.Kind <= TOKEN_OP_SHR { 193 | tokenVal = fmt.Sprintf("<%s,->", token.Content) 194 | } else if token.Kind == TOKEN_IDENTIFIER { 195 | tokenVal = fmt.Sprintf("", token.Value) 196 | } else if token.Kind == TOKEN_NUMBER { 197 | tokenVal = fmt.Sprintf("", token.Value) 198 | } else if token.Kind == TOKEN_STRING { 199 | tokenVal = fmt.Sprintf("", token.Value) 200 | } else { 201 | tokenVal = fmt.Sprintf("<%s,->", token.Value) 202 | } 203 | return fmt.Sprintf("%s %d:%d", tokenVal, token.Line, token.Kth) 204 | } 205 | 206 | var OP_SEP_TOKEN_MAP = map[int]string{ 207 | TOKEN_SEP_DOT: ".", 208 | TOKEN_SEP_VARARG: "...", 209 | TOKEN_SEP_COLON: ":", 210 | TOKEN_SEP_SEMI: ";", 211 | TOKEN_SEP_COMMA: ",", 212 | TOKEN_SEP_QMARK: "?", 213 | TOKEN_SEP_LBRACK: "[", 214 | TOKEN_SEP_RBRACK: "]", 215 | TOKEN_SEP_LPAREN: "(", 216 | TOKEN_SEP_RPAREN: ")", 217 | TOKEN_SEP_LCURLY: "{", 218 | TOKEN_SEP_RCURLY: "}", 219 | TOKEN_OP_ADD: "+", 220 | TOKEN_OP_SUB: "-", 221 | TOKEN_OP_MUL: "*", 222 | TOKEN_OP_DIV: "/", 223 | TOKEN_OP_IDIV: "//", 224 | TOKEN_OP_MOD: "%", 225 | TOKEN_OP_LAND: "&&", 226 | TOKEN_OP_LOR: "||", 227 | TOKEN_OP_AND: "&", 228 | TOKEN_OP_OR: "|", 229 | TOKEN_OP_XOR: "^", 230 | TOKEN_OP_NOT: "~", 231 | TOKEN_OP_LNOT: "!", 232 | TOKEN_OP_EQ: "==", 233 | TOKEN_OP_NE: "!=", 234 | TOKEN_OP_LT: "<", 235 | TOKEN_OP_GT: ">", 236 | TOKEN_OP_LE: "<=", 237 | TOKEN_OP_GE: ">=", 238 | TOKEN_OP_ASSIGN: "=", 239 | TOKEN_OP_INC: "++", 240 | TOKEN_OP_DEC: "--", 241 | TOKEN_OP_ADDEQ: "+=", 242 | TOKEN_OP_SUBEQ: "-=", 243 | TOKEN_OP_MULEQ: "*=", 244 | TOKEN_OP_DIVEQ: "/=", 245 | TOKEN_OP_MODEQ: "%=", 246 | TOKEN_OP_ANDEQ: "&=", 247 | TOKEN_OP_OREQ: "|=", 248 | TOKEN_OP_XOREQ: "^=", 249 | TOKEN_OP_SHL: "<<", 250 | TOKEN_OP_SHR: ">>", 251 | } 252 | 253 | var NUMBER_TOKEN_MAP = map[string]interface{}{ 254 | "0x1111": 0x1111, 255 | "0": 0, 256 | "0xFFFF": 0xffff, 257 | "0xffff": 0xffff, 258 | "0xabc8": 0xabc8, 259 | "0765": 0765, 260 | "01110": 01110, 261 | "985": 985, 262 | "211": 211, 263 | "07654321": 07654321, 264 | "996": 996, 265 | "0.786": 0.786, 266 | "10.212": 10.212, 267 | "1.212": 1.212, 268 | "19.212": 19.212, 269 | } 270 | 271 | var STRING_TOKEN_MAP = map[string]string{ 272 | `""`: "", 273 | `"abc"`: "abc", 274 | `"hello world!"`: "hello world!", 275 | `"hello\tworld!"`: "hello\tworld!", 276 | `"\thello world!"`: "\thello world!", 277 | `"hello world!\t"`: "hello world!\t", 278 | `"\""`: `"`, 279 | `"\'"`: `'`, 280 | `"\n"`: "\n", 281 | `"\t"`: "\t", 282 | `"\\"`: `\`, 283 | `"\\\\"`: `\\`, 284 | `"\\\""`: `\"`, 285 | `''`: "", 286 | `'abc'`: "abc", 287 | `'hello world!'`: "hello world!", 288 | `'hello\tworld!'`: "hello\tworld!", 289 | `'\"'`: `"`, 290 | `'\''`: `'`, 291 | `'\n'`: "\n", 292 | `'\t'`: "\t", 293 | `'\\'`: `\`, 294 | `'\\\\'`: `\\`, 295 | `'\\\"'`: `\"`, 296 | } 297 | 298 | var IDENTIFIER_TOKEN_MAP = map[string]bool{ 299 | "_abc": true, 300 | "abc": true, 301 | "a_b_c": true, 302 | "__abc__": true, 303 | "a123": true, 304 | "a1_23": true, 305 | "a1_23_bc8": true, 306 | } 307 | -------------------------------------------------------------------------------- /compiler/parser/exp.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "gscript/compiler/ast" 5 | "gscript/compiler/token" 6 | ) 7 | 8 | /* 9 | exp ::= | '(' exp ')' | literal | nil | new ID ['(' [expList] ')'] | ID | unOP exp 10 | | exp '--' 11 | | exp '++' 12 | | exp '.' ID 13 | | exp '[' exp ']' 14 | | exp binOP exp 15 | | exp '?' exp ':' exp 16 | | exp '(' [expList] ')' 17 | */ 18 | 19 | /* 20 | exp ::= term12 21 | term12 ::= term11 [ '?' term11 ':' term11] // nesting of ternary operators is not allowed 22 | term11 ::= term10 { '||' term10 } 23 | term10 ::= term9 { '&&' term9 } 24 | term9 ::= term8 { '|' term8 } 25 | term8 ::= term7 { '^' term7 } 26 | term7 ::= term6 { '&' term6 } 27 | term6 ::= term5 { ('==' | '!=') term5 } 28 | term5 ::= term4 { ( '>' | '<' | '>=' | '<=') term4 } 29 | term4 ::= term3 { ( '<<' | '>>' ) term3 } 30 | term3 ::= term2 { ( '+' | '-') term2 } 31 | term2 ::= term1 { ( '/' | '*' | '%' | '//' ) term1 } 32 | term1 ::= { ( '-' | '~ | '!' ) } term0 33 | term0 ::= factor | ( '++' | '--') factor | factor ('++' | '--') 34 | factor ::= '(' exp ')' | literal | nil | new ID ['(' [expList] ')'] 35 | | ID { ( '.' ID | '[' exp ']' | '(' [expList] ')' ) } 36 | */ 37 | 38 | // '(' [explist] ') 39 | func parseExpListBlock(p *Parser) []ast.Exp { 40 | p.NextTokenKind(token.TOKEN_SEP_LPAREN) 41 | if p.Expect(token.TOKEN_SEP_RPAREN) { 42 | p.l.NextToken() 43 | return nil 44 | } 45 | exps := parseExpList(p) 46 | p.NextTokenKind(token.TOKEN_SEP_RPAREN) 47 | return exps 48 | } 49 | 50 | func parseExpList(p *Parser) (exps []ast.Exp) { 51 | exps = append(exps, parseExp(p)) 52 | for p.Expect(token.TOKEN_SEP_COMMA) { 53 | p.l.NextToken() 54 | exps = append(exps, parseExp(p)) 55 | } 56 | return 57 | } 58 | 59 | func parseExp(p *Parser) ast.Exp { 60 | return parseTerm12(p) 61 | } 62 | 63 | func parseTerm12(p *Parser) ast.Exp { 64 | exp1 := parseTerm11(p) 65 | if !p.Expect(token.TOKEN_SEP_QMARK) { // ? 66 | return exp1 67 | } 68 | p.l.NextToken() 69 | exp2 := parseTerm11(p) 70 | p.NextTokenKind(token.TOKEN_SEP_COLON) // : 71 | return &ast.TernaryOpExp{ 72 | Exp1: exp1, 73 | Exp2: exp2, 74 | Exp3: parseTerm11(p), 75 | } 76 | } 77 | 78 | func parseTerm11(p *Parser) ast.Exp { 79 | return _parseBinExp(p, []int{token.TOKEN_OP_LOR}, parseTerm10) 80 | } 81 | 82 | func parseTerm10(p *Parser) ast.Exp { 83 | return _parseBinExp(p, []int{token.TOKEN_OP_LAND}, parseTerm9) 84 | } 85 | 86 | func parseTerm9(p *Parser) ast.Exp { 87 | return _parseBinExp(p, []int{token.TOKEN_OP_OR}, parseTerm8) 88 | } 89 | 90 | func parseTerm8(p *Parser) ast.Exp { 91 | return _parseBinExp(p, []int{token.TOKEN_OP_XOR}, parseTerm7) 92 | } 93 | 94 | func parseTerm7(p *Parser) ast.Exp { 95 | return _parseBinExp(p, []int{token.TOKEN_OP_AND}, parseTerm6) 96 | } 97 | 98 | func parseTerm6(p *Parser) ast.Exp { 99 | return _parseBinExp(p, []int{token.TOKEN_OP_EQ, token.TOKEN_OP_NE}, parseTerm5) 100 | } 101 | 102 | func parseTerm5(p *Parser) ast.Exp { 103 | return _parseBinExp(p, []int{token.TOKEN_OP_LE, token.TOKEN_OP_GE, token.TOKEN_OP_LT, token.TOKEN_OP_GT}, parseTerm4) 104 | } 105 | 106 | func parseTerm4(p *Parser) ast.Exp { 107 | return _parseBinExp(p, []int{token.TOKEN_OP_SHL, token.TOKEN_OP_SHR}, parseTerm3) 108 | } 109 | 110 | func parseTerm3(p *Parser) ast.Exp { 111 | return _parseBinExp(p, []int{token.TOKEN_OP_ADD, token.TOKEN_OP_SUB}, parseTerm2) 112 | } 113 | 114 | func parseTerm2(p *Parser) ast.Exp { 115 | return _parseBinExp(p, []int{token.TOKEN_OP_DIV, token.TOKEN_OP_MUL, token.TOKEN_OP_MOD, token.TOKEN_OP_IDIV}, parseTerm1) 116 | } 117 | 118 | func parseTerm1(p *Parser) ast.Exp { 119 | kind := p.l.LookAhead().Kind 120 | 121 | var unOp int 122 | if kind == token.TOKEN_OP_SUB { 123 | unOp = ast.UNOP_NEG 124 | } else if kind == token.TOKEN_OP_NOT { 125 | unOp = ast.UNOP_NOT 126 | } else if kind == token.TOKEN_OP_LNOT { 127 | unOp = ast.UNOP_LNOT 128 | } else { 129 | return parseTerm0(p) 130 | } 131 | p.l.NextToken() 132 | return &ast.UnOpExp{Op: unOp, Exp: parseTerm1(p)} 133 | } 134 | 135 | func parseTerm0(p *Parser) ast.Exp { 136 | if p.Expect(token.TOKEN_OP_INC) { 137 | p.l.NextToken() 138 | return &ast.UnOpExp{Op: ast.UNOP_INC, Exp: parseFactor(p)} 139 | } else if p.Expect(token.TOKEN_OP_DEC) { 140 | p.l.NextToken() 141 | return &ast.UnOpExp{Op: ast.UNOP_DEC, Exp: parseFactor(p)} 142 | } 143 | exp := parseFactor(p) 144 | if p.Expect(token.TOKEN_OP_INC) { 145 | p.l.NextToken() 146 | return &ast.UnOpExp{Op: ast.UNOP_INC_, Exp: exp} 147 | } else if p.Expect(token.TOKEN_OP_DEC) { 148 | p.l.NextToken() 149 | return &ast.UnOpExp{Op: ast.UNOP_DEC_, Exp: exp} 150 | } 151 | return exp 152 | } 153 | 154 | // factor ::= '(' exp ')' | literal | nil | new ID ['(' [expList] ')'] 155 | // | ID { ( '.' ID | '[' exp ']' | '(' [expList] ')' ) } 156 | func parseFactor(p *Parser) ast.Exp { 157 | switch ahead := p.l.LookAhead(); ahead.Kind { 158 | case token.TOKEN_STRING: 159 | return parseStringLiteralExp(p) 160 | case token.TOKEN_NUMBER: 161 | return parseNumberLiteralExp(p) 162 | case token.TOKEN_SEP_LCURLY: // mapLiteral 163 | return parseMapLiteralExp(p) 164 | case token.TOKEN_SEP_LBRACK: // arrLiteral 165 | return parseArrLiteralExp(p) 166 | case token.TOKEN_KW_TRUE: 167 | return parseTrueExp(p) 168 | case token.TOKEN_KW_FALSE: 169 | return parseFalseExp(p) 170 | case token.TOKEN_KW_NIL: 171 | return parseNilExp(p) 172 | case token.TOKEN_KW_FUNC: 173 | return parseFuncLiteralExp(p) 174 | case token.TOKEN_KW_NEW: 175 | return parseNewObjectExp(p) 176 | case token.TOKEN_IDENTIFIER: 177 | return parseFuncCallOrAttrExp(p) 178 | case token.TOKEN_SEP_LPAREN: // (exp) 179 | p.l.NextToken() 180 | exp := parseExp(p) 181 | p.NextTokenKind(token.TOKEN_SEP_RPAREN) 182 | return exp 183 | default: 184 | p.exit("unexpected token '%s' for parsing factor", ahead.Content) 185 | } 186 | return nil 187 | } 188 | 189 | func parseFuncLiteralExp(p *Parser) *ast.FuncLiteralExp { 190 | p.l.NextToken() 191 | return &ast.FuncLiteralExp{FuncLiteral: p.parseFuncLiteral()} 192 | } 193 | 194 | func parseNilExp(p *Parser) *ast.NilExp { 195 | p.l.NextToken() 196 | return &ast.NilExp{} 197 | } 198 | 199 | func parseFalseExp(p *Parser) *ast.FalseExp { 200 | p.l.NextToken() 201 | return &ast.FalseExp{} 202 | } 203 | 204 | func parseTrueExp(p *Parser) *ast.TrueExp { 205 | p.l.NextToken() 206 | return &ast.TrueExp{} 207 | } 208 | 209 | func parseNumberLiteralExp(p *Parser) *ast.NumberLiteralExp { 210 | return &ast.NumberLiteralExp{Value: p.l.NextToken().Value} 211 | } 212 | 213 | func parseStringLiteralExp(p *Parser) *ast.StringLiteralExp { 214 | return &ast.StringLiteralExp{Value: p.l.NextToken().Value.(string)} 215 | } 216 | 217 | func parseFuncCallOrAttrExp(p *Parser) ast.Exp { 218 | var exp ast.Exp 219 | exp = &ast.NameExp{ 220 | Line: p.l.Line(), 221 | Name: p.l.NextToken().Content, 222 | } 223 | for { 224 | switch p.l.LookAhead().Kind { 225 | case token.TOKEN_SEP_DOT: // access attribute 226 | p.l.NextToken() 227 | exp = &ast.BinOpExp{ 228 | Exp1: exp, 229 | Exp2: &ast.StringLiteralExp{ 230 | Value: p.NextTokenKind(token.TOKEN_IDENTIFIER).Content, 231 | }, 232 | BinOp: ast.BINOP_ATTR, 233 | } 234 | case token.TOKEN_SEP_LBRACK: // access map 235 | p.l.NextToken() 236 | exp = &ast.BinOpExp{ 237 | Exp1: exp, 238 | Exp2: parseExp(p), 239 | BinOp: ast.BINOP_ATTR, 240 | } 241 | p.NextTokenKind(token.TOKEN_SEP_RBRACK) 242 | case token.TOKEN_SEP_LPAREN: // function call 243 | exp = &ast.FuncCallExp{Func: exp, Args: parseExpListBlock(p)} 244 | default: 245 | return exp 246 | } 247 | } 248 | } 249 | 250 | func parseNewObjectExp(p *Parser) *ast.NewObjectExp { 251 | p.l.NextToken() 252 | exp := &ast.NewObjectExp{} 253 | exp.Name = p.NextTokenKind(token.TOKEN_IDENTIFIER).Content 254 | exp.Line = p.l.Line() 255 | if !p.Expect(token.TOKEN_SEP_LPAREN) { 256 | return exp 257 | } 258 | exp.Args = parseExpListBlock(p) 259 | return exp 260 | } 261 | 262 | func parseArrLiteralExp(p *Parser) *ast.ArrLiteralExp { 263 | p.l.NextToken() 264 | exp := &ast.ArrLiteralExp{} 265 | for !p.Expect(token.TOKEN_SEP_RBRACK) { 266 | exp.Vals = append(exp.Vals, parseExp(p)) 267 | if p.Expect(token.TOKEN_SEP_COMMA) || p.Expect(token.TOKEN_SEP_SEMI) { 268 | p.l.NextToken() 269 | } 270 | } 271 | p.NextTokenKind(token.TOKEN_SEP_RBRACK) 272 | return exp 273 | } 274 | 275 | func parseMapLiteralExp(p *Parser) *ast.MapLiteralExp { 276 | p.l.NextToken() 277 | exp := &ast.MapLiteralExp{} 278 | loop: 279 | for { 280 | var key interface{} 281 | switch p.l.LookAhead().Kind { 282 | case token.TOKEN_KW_TRUE: 283 | key = true 284 | case token.TOKEN_KW_FALSE: 285 | key = false 286 | case token.TOKEN_STRING, token.TOKEN_NUMBER: 287 | key = p.l.LookAhead().Value 288 | case token.TOKEN_IDENTIFIER: 289 | key = p.l.LookAhead().Content 290 | default: 291 | break loop 292 | } 293 | p.l.NextToken() 294 | exp.Keys = append(exp.Keys, key) 295 | 296 | var val ast.Exp 297 | p.NextTokenKind(token.TOKEN_SEP_COLON) 298 | val = parseExp(p) 299 | exp.Vals = append(exp.Vals, val) 300 | if p.Expect(token.TOKEN_SEP_COMMA) || p.Expect(token.TOKEN_SEP_SEMI) { 301 | p.l.NextToken() 302 | } else if !p.Expect(token.TOKEN_SEP_RCURLY) { 303 | p.exit("expect '}', but got %s", p.l.NextToken().Content) 304 | } 305 | } 306 | p.NextTokenKind(token.TOKEN_SEP_RCURLY) 307 | return exp 308 | } 309 | 310 | func _parseBinExp(p *Parser, expect []int, cb func(*Parser) ast.Exp) ast.Exp { 311 | exp := cb(p) 312 | for { 313 | flag := func() bool { 314 | for _, kind := range expect { 315 | if p.Expect(kind) { 316 | return true 317 | } 318 | } 319 | return false 320 | }() 321 | if !flag { 322 | return exp 323 | } 324 | binExp := &ast.BinOpExp{ 325 | Exp1: exp, 326 | BinOp: p.l.NextToken().Kind, 327 | Exp2: cb(p), 328 | } 329 | exp = binExp 330 | } 331 | } 332 | -------------------------------------------------------------------------------- /compiler/parser/exp_test.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | . "gscript/compiler/ast" 5 | "gscript/compiler/lexer" 6 | "reflect" 7 | "testing" 8 | ) 9 | 10 | func newLexer(src string) *lexer.Lexer { 11 | return lexer.NewLexer("", []byte(src)) 12 | } 13 | 14 | func TestParseTerm12(t *testing.T) { 15 | srcs := []string{ 16 | `a||c?b+d:c`, 17 | `a?(b?c:d):e`, 18 | } 19 | var wants = []*TernaryOpExp{ 20 | {&BinOpExp{BINOP_LOR, &NameExp{1, "a"}, &NameExp{1, "c"}}, &BinOpExp{BINOP_ADD, &NameExp{1, "b"}, &NameExp{1, "d"}}, &NameExp{1, "c"}}, 21 | {&NameExp{1, "a"}, &TernaryOpExp{&NameExp{1, "b"}, &NameExp{1, "c"}, &NameExp{1, "d"}}, &NameExp{1, "e"}}, 22 | } 23 | for i, src := range srcs { 24 | l := newLexer(src) 25 | exp := parseTerm12(NewParser(l)) 26 | if !reflect.DeepEqual(wants[i], exp) { 27 | t.Fatalf("parse term12 failed:\n%s\n", src) 28 | } 29 | } 30 | } 31 | 32 | func TestParseTerm11(t *testing.T) { 33 | srcs := []string{ 34 | `a||b||c`, 35 | `a||b&&c`, 36 | } 37 | var wants = []*BinOpExp{ 38 | {BINOP_LOR, &BinOpExp{BINOP_LOR, &NameExp{1, "a"}, &NameExp{1, "b"}}, &NameExp{1, "c"}}, 39 | {BINOP_LOR, &NameExp{1, "a"}, &BinOpExp{BINOP_LAND, &NameExp{1, "b"}, &NameExp{1, "c"}}}, 40 | } 41 | for i, src := range srcs { 42 | l := newLexer(src) 43 | exp := parseTerm11(NewParser(l)) 44 | if !reflect.DeepEqual(wants[i], exp) { 45 | t.Fatalf("parse term11 failed:\n%s\n", src) 46 | } 47 | } 48 | } 49 | 50 | func TestParseTerm10(t *testing.T) { 51 | srcs := []string{ 52 | `a&&b`, 53 | `a&&b|c`, 54 | } 55 | var wants = []*BinOpExp{ 56 | {BINOP_LAND, &NameExp{1, "a"}, &NameExp{1, "b"}}, 57 | {BINOP_LAND, &NameExp{1, "a"}, &BinOpExp{BINOP_OR, &NameExp{1, "b"}, &NameExp{1, "c"}}}, 58 | } 59 | for i, src := range srcs { 60 | l := newLexer(src) 61 | exp := parseTerm10(NewParser(l)) 62 | if !reflect.DeepEqual(wants[i], exp) { 63 | t.Fatalf("parse term10 failed:\n%s\n", src) 64 | } 65 | } 66 | } 67 | 68 | func TestParseTerm9(t *testing.T) { 69 | srcs := []string{ 70 | `a|b`, 71 | `a|b^c`, 72 | } 73 | var wants = []*BinOpExp{ 74 | {BINOP_OR, &NameExp{1, "a"}, &NameExp{1, "b"}}, 75 | {BINOP_OR, &NameExp{1, "a"}, &BinOpExp{BINOP_XOR, &NameExp{1, "b"}, &NameExp{1, "c"}}}, 76 | } 77 | for i, src := range srcs { 78 | l := newLexer(src) 79 | exp := parseTerm9(NewParser(l)) 80 | if !reflect.DeepEqual(wants[i], exp) { 81 | t.Fatalf("parse term9 failed:\n%s\n", src) 82 | } 83 | } 84 | } 85 | 86 | func TestParseTerm8(t *testing.T) { 87 | srcs := []string{ 88 | `a^b`, 89 | `a^b&c`, 90 | } 91 | var wants = []*BinOpExp{ 92 | {BINOP_XOR, &NameExp{1, "a"}, &NameExp{1, "b"}}, 93 | {BINOP_XOR, &NameExp{1, "a"}, &BinOpExp{BINOP_AND, &NameExp{1, "b"}, &NameExp{1, "c"}}}, 94 | } 95 | for i, src := range srcs { 96 | l := newLexer(src) 97 | exp := parseTerm8(NewParser(l)) 98 | if !reflect.DeepEqual(wants[i], exp) { 99 | t.Fatalf("parse term8 failed:\n%s\n", src) 100 | } 101 | } 102 | } 103 | 104 | func TestParseTerm7(t *testing.T) { 105 | srcs := []string{ 106 | `a&b`, 107 | `a&b==c`, 108 | } 109 | var wants = []*BinOpExp{ 110 | {BINOP_AND, &NameExp{1, "a"}, &NameExp{1, "b"}}, 111 | {BINOP_AND, &NameExp{1, "a"}, &BinOpExp{BINOP_EQ, &NameExp{1, "b"}, &NameExp{1, "c"}}}, 112 | } 113 | for i, src := range srcs { 114 | l := newLexer(src) 115 | exp := parseTerm7(NewParser(l)) 116 | if !reflect.DeepEqual(wants[i], exp) { 117 | t.Fatalf("parse term7 failed:\n%s\n", src) 118 | } 119 | } 120 | } 121 | 122 | func TestParseTerm6(t *testing.T) { 123 | srcs := []string{ 124 | `a==b`, 125 | `a!=b==c`, 126 | `a==b>=c`, 127 | } 128 | var wants = []*BinOpExp{ 129 | {BINOP_EQ, &NameExp{1, "a"}, &NameExp{1, "b"}}, 130 | {BINOP_EQ, &BinOpExp{BINOP_NE, &NameExp{1, "a"}, &NameExp{1, "b"}}, &NameExp{1, "c"}}, 131 | {BINOP_EQ, &NameExp{1, "a"}, &BinOpExp{BINOP_GE, &NameExp{1, "b"}, &NameExp{1, "c"}}}, 132 | } 133 | for i, src := range srcs { 134 | l := newLexer(src) 135 | exp := parseTerm6(NewParser(l)) 136 | if !reflect.DeepEqual(wants[i], exp) { 137 | t.Fatalf("parse term6 failed:\n%s\n", src) 138 | } 139 | } 140 | } 141 | 142 | func TestParseTerm5(t *testing.T) { 143 | srcs := []string{ 144 | `a > b<=8>9`, 148 | } 149 | var wants = []*BinOpExp{ 150 | {BINOP_GT, &NameExp{1, "a"}, &BinOpExp{BINOP_SHL, &NameExp{1, "b"}, &NameExp{1, "c"}}}, 151 | {BINOP_LE, &NumberLiteralExp{int64(1)}, &NumberLiteralExp{int64(3)}}, 152 | {BINOP_LT, &NumberLiteralExp{int64(2)}, &NumberLiteralExp{int64(4)}}, 153 | {BINOP_GT, &BinOpExp{BINOP_GE, &NameExp{1, "a"}, &NumberLiteralExp{int64(8)}}, &NumberLiteralExp{int64(9)}}, 154 | } 155 | for i, src := range srcs { 156 | l := newLexer(src) 157 | exp := parseTerm5(NewParser(l)) 158 | if !reflect.DeepEqual(wants[i], exp) { 159 | t.Fatalf("parse term5 failed:\n%s\n", src) 160 | } 161 | } 162 | } 163 | 164 | func TestParseTerm4(t *testing.T) { 165 | srcs := []string{ 166 | `a<>b<> 36 | TOKEN_OP_SHL // << 37 | TOKEN_OP_LE // <= 38 | TOKEN_OP_GE // >= 39 | TOKEN_OP_LT // < 40 | TOKEN_OP_GT // > 41 | TOKEN_OP_EQ // == 42 | TOKEN_OP_NE // != 43 | TOKEN_OP_LAND // && 44 | TOKEN_OP_LOR // || 45 | TOKEN_BINOP_END 46 | 47 | TOKEN_OP_NOT // ~ 48 | TOKEN_OP_LNOT // ! 49 | TOKEN_OP_INC // ++ 50 | TOKEN_OP_DEC // -- 51 | 52 | // don't change the order of constants between TOKEN_ASIGN_START and TOKEN_ASIGN_END 53 | TOKEN_ASIGN_START 54 | TOKEN_OP_ASSIGN // = 55 | TOKEN_OP_ADDEQ // += 56 | TOKEN_OP_SUBEQ // -= 57 | TOKEN_OP_MULEQ // *= 58 | TOKEN_OP_DIVEQ // /= 59 | TOKEN_OP_MODEQ // %= 60 | TOKEN_OP_ANDEQ // &= 61 | TOKEN_OP_XOREQ // ^= 62 | TOKEN_OP_OREQ // |= 63 | TOKEN_ASIGN_END 64 | 65 | // keywords 66 | TOKEN_KW_BREAK // break 67 | TOKEN_KW_CONTINUE // continue 68 | TOKEN_KW_FOR // for 69 | TOKEN_KW_WHILE // while 70 | TOKEN_KW_IF // if 71 | TOKEN_KW_ELIF // elif 72 | TOKEN_KW_ELSE // else 73 | TOKEN_KW_SWITCH // switch 74 | TOKEN_KW_CASE // case 75 | TOKEN_KW_FALLTHROUGH // fallthrough 76 | TOKEN_KW_DEFAULT // default 77 | TOKEN_KW_RETURN // return 78 | TOKEN_KW_FUNC // func 79 | TOKEN_KW_LET // let 80 | TOKEN_KW_TRUE // true 81 | TOKEN_KW_FALSE // false 82 | TOKEN_KW_NEW // new 83 | TOKEN_KW_NIL // nil 84 | TOKEN_KW_CLASS // class 85 | TOKEN_KW_ENUM // enum 86 | TOKEN_KW_LOOP // loop 87 | TOKEN_KW_IMPORT // import 88 | TOKEN_KW_EXPORT // export 89 | TOKEN_KW_AS // as 90 | TOKEN_KW_GOTO // goto 91 | TOKEN_KW_TRY // try 92 | TOKEN_KW_CATCH // catch 93 | ) 94 | 95 | var TokenDescs = map[int]string{ 96 | TOKEN_IDENTIFIER: "identifier", 97 | TOKEN_NUMBER: "number", 98 | TOKEN_STRING: "string", 99 | TOKEN_SEP_DOT: ".", 100 | TOKEN_SEP_VARARG: "...", 101 | TOKEN_SEP_COLON: ":", 102 | TOKEN_SEP_SEMI: ";", 103 | TOKEN_SEP_COMMA: ",", 104 | TOKEN_SEP_QMARK: "?", 105 | TOKEN_SEP_LBRACK: "[", 106 | TOKEN_SEP_RBRACK: "]", 107 | TOKEN_SEP_LPAREN: "(", 108 | TOKEN_SEP_RPAREN: ")", 109 | TOKEN_SEP_LCURLY: "{", 110 | TOKEN_SEP_RCURLY: "}", 111 | TOKEN_OP_ADD: "+", 112 | TOKEN_OP_SUB: "-", 113 | TOKEN_OP_MUL: "*", 114 | TOKEN_OP_DIV: "/", 115 | TOKEN_OP_MOD: "%", 116 | TOKEN_OP_AND: "&", 117 | TOKEN_OP_XOR: "^", 118 | TOKEN_OP_OR: "|", 119 | TOKEN_OP_IDIV: "//", 120 | TOKEN_OP_SHR: ">>", 121 | TOKEN_OP_SHL: "<<", 122 | TOKEN_OP_LE: "<=", 123 | TOKEN_OP_GE: ">=", 124 | TOKEN_OP_LT: "<", 125 | TOKEN_OP_GT: ">", 126 | TOKEN_OP_EQ: "==", 127 | TOKEN_OP_NE: "!=", 128 | TOKEN_OP_LAND: "&&", 129 | TOKEN_OP_LOR: "||", 130 | TOKEN_OP_NOT: "~", 131 | TOKEN_OP_LNOT: "!", 132 | TOKEN_OP_INC: "++", 133 | TOKEN_OP_DEC: "--", 134 | TOKEN_OP_ASSIGN: "=", 135 | TOKEN_OP_ADDEQ: "+=", 136 | TOKEN_OP_SUBEQ: "-=", 137 | TOKEN_OP_MULEQ: "*=", 138 | TOKEN_OP_DIVEQ: "/=", 139 | TOKEN_OP_MODEQ: "%=", 140 | TOKEN_OP_ANDEQ: "&=", 141 | TOKEN_OP_XOREQ: "^=", 142 | TOKEN_OP_OREQ: "|=", 143 | TOKEN_KW_BREAK: "break", 144 | TOKEN_KW_CONTINUE: "continue", 145 | TOKEN_KW_FOR: "for", 146 | TOKEN_KW_WHILE: "while", 147 | TOKEN_KW_IF: "if", 148 | TOKEN_KW_ELIF: "elif", 149 | TOKEN_KW_ELSE: "else", 150 | TOKEN_KW_SWITCH: "switch", 151 | TOKEN_KW_CASE: "case", 152 | TOKEN_KW_FALLTHROUGH: "fallthrough", 153 | TOKEN_KW_DEFAULT: "default", 154 | TOKEN_KW_RETURN: "return", 155 | TOKEN_KW_FUNC: "func", 156 | TOKEN_KW_LET: "let", 157 | TOKEN_KW_TRUE: "true", 158 | TOKEN_KW_FALSE: "false", 159 | TOKEN_KW_NEW: "new", 160 | TOKEN_KW_NIL: "nil", 161 | TOKEN_KW_CLASS: "class", 162 | TOKEN_KW_ENUM: "enum", 163 | TOKEN_KW_LOOP: "loop", 164 | TOKEN_KW_IMPORT: "import", 165 | TOKEN_KW_EXPORT: "export", 166 | TOKEN_KW_AS: "as", 167 | TOKEN_KW_GOTO: "goto", 168 | TOKEN_KW_TRY: "try", 169 | TOKEN_KW_CATCH: "catch", 170 | } 171 | 172 | var _eofToken = Token{Kind: TOKEN_EOF} 173 | var EOFToken = &_eofToken 174 | 175 | type Token struct { 176 | Kind int // token kind 177 | Line int // line number 178 | Kth int // index of first charactor of Content in current line 179 | Content string // original string 180 | Value interface{} // token value, string literal, number literal or identifier 181 | } 182 | 183 | var Keywords = map[string]int{ 184 | "break": TOKEN_KW_BREAK, 185 | "continue": TOKEN_KW_CONTINUE, 186 | "for": TOKEN_KW_FOR, 187 | "while": TOKEN_KW_WHILE, 188 | "if": TOKEN_KW_IF, 189 | "elif": TOKEN_KW_ELIF, 190 | "else": TOKEN_KW_ELSE, 191 | "switch": TOKEN_KW_SWITCH, 192 | "case": TOKEN_KW_CASE, 193 | "fallthrough": TOKEN_KW_FALLTHROUGH, 194 | "default": TOKEN_KW_DEFAULT, 195 | "return": TOKEN_KW_RETURN, 196 | "func": TOKEN_KW_FUNC, 197 | "let": TOKEN_KW_LET, 198 | "true": TOKEN_KW_TRUE, 199 | "false": TOKEN_KW_FALSE, 200 | "new": TOKEN_KW_NEW, 201 | "nil": TOKEN_KW_NIL, 202 | "class": TOKEN_KW_CLASS, 203 | "enum": TOKEN_KW_ENUM, 204 | "loop": TOKEN_KW_LOOP, 205 | "import": TOKEN_KW_IMPORT, 206 | "export": TOKEN_KW_EXPORT, 207 | "as": TOKEN_KW_AS, 208 | "goto": TOKEN_KW_GOTO, 209 | "try": TOKEN_KW_TRY, 210 | "catch": TOKEN_KW_CATCH, 211 | } 212 | -------------------------------------------------------------------------------- /doc/bnf.txt: -------------------------------------------------------------------------------- 1 | program ::= {importBlock} {blockStmt} [exportBlock] 2 | 3 | blockStmt ::= block ';' 4 | | stmt 5 | 6 | block ::= '{' {blockStmt} '}' 7 | 8 | importBlock ::= import {',' } [as ID {',' ID}] ';' 9 | 10 | exportBlock ::= export exp ';' 11 | 12 | stmt ::= | varDeclare ';' // declare variables 13 | | varAssign ';' // variables assignment 14 | | varIncOrDec ';' 15 | | var '(' {explist} ')' callTail ';' // named function call 16 | | ID ':' // label 17 | | func ID funcBody ';' // function definition 18 | | funcLiteral '(' [expList] ')' callTail ';' // anonymous function call 19 | | break ';' 20 | | continue ';' 21 | | return [expList] ';' 22 | | goto ID ';' 23 | | fallthrough ';' 24 | | loop '(' let ID [',' ID] ':' exp ')' blockStmt // iterator loop 25 | | while expBlock blockStmt // while statement 26 | | for '(' ';' exp ';' forTail ')' blockStmt // for statement 27 | | if expBlock blockStmt {elif expBlock blockStmt} [else blockStmt] 28 | | class ID '{' {classBody} '}' ';' 29 | | enum '{' [enumBlocks] '}' ';' 30 | | switch expBlock [';'] '{' caseBlocks '}' 31 | | incOrDecVar ';' 32 | | try '{' {blockStmt} '}' catch '{ {blockStmt} '}' finally '{' {blockStmt} '}' 33 | 34 | varDeclare ::= nameList '=' expList] 35 | varAssign ::= var {',' var} assignOP expList 36 | varIncOrDec ::= var <'++'|'--'> 37 | incOrDecVar ::= <'++'|'--'> var 38 | forTail ::= varAssign | varIncOrDec | incOrDecVar 39 | 40 | callTail ::= { {attrTail} '(' {explist} ')' } 41 | 42 | attrTail ::= '.' ID 43 | | '[' exp ']' 44 | 45 | varList ::= var {',' var} 46 | var ::= ID {attrTail} 47 | 48 | funcBody ::= '(' [parlist] ')' [';'] '{' {blockStmt} '}' 49 | 50 | expBlock ::= '(' exp ')' 51 | 52 | nameList ::= ID {,ID} 53 | expList ::= exp {,exp} 54 | exp ::= | '(' exp ')' | literal | nil | new ID ['(' [expList] ')'] | ID | unOP exp 55 | | exp '--' 56 | | exp '++' 57 | | exp '.' ID 58 | | exp '[' exp ']' 59 | | exp binOP exp 60 | | exp '?' exp ':' exp 61 | | exp '(' [expList] ')' 62 | 63 | classBody ::= | ID ['=' exp] 64 | | ID funcBody 65 | 66 | enumBlocks ::= enumBlock {',' enumBlock} [','] 67 | enumBlock :: ID ['=' NUMBER] 68 | 69 | caseBlocks ::= {caseBlock} [default ':' {block}] 70 | caseBlock ::= case exp {',' exp} ':' {block} 71 | 72 | parlist ::= par {',' par} ['...' ID] // function parameter 73 | par ::= ID ['=' constLiteral] 74 | 75 | literal ::= mapLiteral | constLiteral | arrLiteral | funcLiteral 76 | 77 | mapLiteral ::= '{' [fields] '}' 78 | fields ::= field {',' field} [','] 79 | field ::= ':' exp 80 | 81 | constLiteral ::= STRING | NUMBER | false | true 82 | 83 | arrLiteral ::= '[' [expList] ']' 84 | 85 | funcLiteral ::= func funcBody 86 | 87 | binOP ::= '+' | '-' | '*' | '/' | '//' | '%' | '&' | '|' 88 | | '^' | '>>' | '<<' | '<=' | '>=' | '<' | '>' 89 | | '==' | '!=' | '&&' | '||' 90 | 91 | unOP ::= '~' | '!' | '-' | '--' | '++' 92 | 93 | assignOP ::= '=' | '+=' | '-=' | ':=' | '/=' | '*=' 94 | | '%=' | '&=' | '^=' | '|=' 95 | -------------------------------------------------------------------------------- /doc/builtin.md: -------------------------------------------------------------------------------- 1 | ## Builtin Functions 2 | 3 | ### print 4 | 5 | ```python 6 | print(1,"2",[1,2,3],{foo:"bar"}) # 1 2 Array[1, 2, 3] Object{foo: bar} 7 | ``` 8 | 9 | + parameter 10 | + count: `>=0` 11 | + type: any 12 | 13 | ### len 14 | 15 | ```python 16 | let l = len([1,2,3]) # l = 3 17 | l = len({foo:"bar"}) # l = 1 18 | l = len("hello") # l = 5 19 | ``` 20 | 21 | + parameter 22 | + count: `1` 23 | + type: `String`, `Array`, `Object` or `Buffer` 24 | 25 | + return 26 | + count: `1` 27 | + type: `Number` 28 | 29 | ### append 30 | 31 | ```python 32 | let arr = [] 33 | append(arr,1,2) # arr = [1,2] 34 | ``` 35 | 36 | + parameter 37 | + count: `>=2` 38 | + type: `arg0(Array)`,`arg1(any)`, `arg2(any)`, ... ,`argn(any)` 39 | 40 | ### sub 41 | 42 | sub(src, start[,end]) 43 | 44 | ```python 45 | let str = "1234" 46 | let s1 = sub(str,1) # s1 = "234" 47 | let s2 = sub(str,1,2) # s2 = "2" 48 | 49 | let arr = [1,2,3,4] 50 | ler a1 = sub(arr,1) # a1 = [2,3,4] 51 | let a2 = sub(arr,1,2) # a2 = [2] 52 | ``` 53 | 54 | + description: get sub array or sub string. 55 | + parameter 56 | + count: `2` or `3` 57 | + type: `arg0(String or Array)`, `start(Integer)`, `end(Integer)` 58 | 59 | + return 60 | + count: 1 61 | + type: `String` or `Array` 62 | 63 | ### type 64 | 65 | ```python 66 | print(type("")) # String 67 | print(type(func(){})) # closure 68 | print(type(type)) # Builtin 69 | print(type({})) # Object 70 | print(type([])) # Array 71 | print(type(false)) # Boolean 72 | print(type(nil)) # Nil 73 | ``` 74 | 75 | ### delete 76 | 77 | ```python 78 | let obj = {foo:"bar"} 79 | delete(obj,"foo") 80 | print(obj.foo) # 81 | ``` 82 | 83 | + parameter 84 | + count: `2` 85 | + type: `arg0(Object)`, `arg1(any)` 86 | 87 | ### clone 88 | 89 | ```python 90 | let arr1 = [1,2,3] 91 | let arr2, arr3 = arr1, clone(arr1) 92 | arr1[0] = -1 93 | print(arr1) # [-1,2,3] 94 | print(arr2) # [-1,2,3] 95 | print(arr3) # [1,2,3] 96 | ``` 97 | 98 | + parameter 99 | + count: `1` 100 | + type: `Array`, `Object`, `Buffer` 101 | + return 102 | + count: `1` 103 | + type: `Array`, `Object`, `Buffer` 104 | 105 | ### throw 106 | 107 | ```python 108 | try{ 109 | let arr = [1,2,3] 110 | throw(arr) 111 | } 112 | catch(e) { 113 | print(e) # [1,2,3] 114 | } 115 | ``` 116 | 117 | ### others 118 | 119 | there are many other builtin functions, but always dangerous to use. So we wrap these apis to standard libraries, please use these libraries instead. 120 | -------------------------------------------------------------------------------- /doc/std.md: -------------------------------------------------------------------------------- 1 | ## Standard Library 2 | 3 | Under development, following are standard library currently provided: 4 | 5 | + [os](https://github.com/gufeijun/gscript/blob/master/doc/std_os.md): platform-independent interface to operating system functionality. 6 | + [Buffer](https://github.com/gufeijun/gscript/blob/master/doc/std_Buffer.md): structure for working with binary data. 7 | + [fs](https://github.com/gufeijun/gscript/blob/master/doc/std_fs.md): file system functions. 8 | 9 | -------------------------------------------------------------------------------- /doc/std_Buffer.md: -------------------------------------------------------------------------------- 1 | ## Standard Library - Buffer 2 | 3 | ```python 4 | import Buffer 5 | ``` 6 | 7 | ### Functions 8 | 9 | + `alloc(cap:Integer) => class Buffer`: make a bytes Buffer with capacity. 10 | + `from(str:String) => class Buffer`: make a bytes Buffer from string. 11 | + `concat(buf1:class Buffer, buf2:class Buffer) => class Buffer` : concat two Buffer. 12 | + `copy(buf1:class Buffer, buf2:class Buffer, length:Integer, start1=0:Integer, start2=0:Integer)`: copy @length bytes from buf2 to buf1. 13 | 14 | ### Buffer 15 | 16 | method: 17 | 18 | + `cap() => Integer`: return capacity of Buffer 19 | + `toString() => String`: bytes data to string 20 | + `slice(offset:Integer, length:Integer) => class Buffer`: get slice of Buffer. Start from offset, ends at offset+length. if length is not specific, ends at Buffer.cap(). 21 | + `readInt8(offset:Integer) => Integer`. 22 | + `readUint8(offset:Integer) => Integer`. 23 | + `readInt16BE(offset:Integer) => Integer`. 24 | + `readInt16LE(offset:Integer) => Integer`. 25 | + `readUint16LE(offset:Integer) => Integer`. 26 | + `readUint16BE(offset:Integer) => Integer`. 27 | + `readInt32LE(offset:Integer) => Integer`. 28 | + `readInt32BE(offset:Integer) => Integer`. 29 | + `readUint32LE(offset:Integer) => Integer`. 30 | + `readUint32BE(offset:Integer) => Integer`. 31 | + `readInt64LE(offset:Integer) => Integer`. 32 | + `readInt64BE(offset:Integer) => Integer`. 33 | + `readUint64LE(offset:Integer) => Integer`. 34 | + `readUint64BE(offset:Integer) => Integer`. 35 | + `write8(offset:Integer, number:Number)`. 36 | + `write16BE(offset:Integer, number:Number)`. 37 | + `write16LE(offset:Integer, number:Number)`. 38 | + `write32BE(offset:Integer, number:Number)`. 39 | + `write32LE(offset:Integer, number:Number)`. 40 | + `write64LE(offset:Integer, number:Number)`. 41 | + `write64BE(offset:Integer, number:Number)`. -------------------------------------------------------------------------------- /doc/std_fs.md: -------------------------------------------------------------------------------- 1 | ## Standard Library - fs 2 | 3 | ```python 4 | import fs 5 | ``` 6 | 7 | ### Functions 8 | 9 | + `open(path:String, flag="r":String, mode=0664:Integer) exception => class File `: open named file with flag and mode. 10 | + flag(`r`): read-only. 11 | + flag(`w`): write-only. 12 | + flag(`wr`): read write. 13 | + flag(`c`): if file do not exist, create file. 14 | + flag(`a`): append. 15 | + flag(`t`): truncate. 16 | + flag(`e`): excl . 17 | + `create(path:String, mode=0664:Integer) exception => class File`: create file with mode. 18 | + `stat(path:String) exception => class stat`: get file stat of named path. member of class stat: 19 | + `is_dir:Bool`. If the file is a directory. 20 | + `mode:Integer`. File mode. 21 | + `name:String`. File name. 22 | + `size:Integer`. File Size. 23 | + `mod_time`. File modify unix time. 24 | + `remove(path:String) exception`: remove the named file. 25 | + `readFile(path:String) exception => class Buffer`: read all the data from named path to Buffer. 26 | + `mkdir(dir:String, mode=0664:Integer) exception`: make directory with named dir. 27 | + `chmod(path:String, mode:Integer) exception`: changes the mode of the named file to mode. 28 | + `chown(path:String, uid:Integer, gid:Integer) exception`: changes the numeric uid and gid of the named file. 29 | + `rename(oldpath:String, newpath:String) exception`: renames (moves) oldpath to newpath. 30 | + `readDir(path:String) exception => Array`: reads the named directory, returning all its directory entries sorted by filename. 31 | 32 | ### File 33 | 34 | method: 35 | 36 | + `read(buf:class Buffer, size:Integer) exception => Integer`: read @size bytes from file to Buffer. If size is not specific, it will try to fill up Buffer. It returns the number of bytes read. 37 | + `write(data:string or class Buffer, size=-1:Integer) exception => Integer`: write @size bytes from data to file. If size==-1, write all bytes of data to file. 38 | + `close() exception`: close file. 39 | + `seek(offset:Integer, whence="cur":String) exception => Integer` : Seek sets the offset for the next Read or Write on file to offset, interpreted according to whence: "start" means relative to the origin of the file, "cur" means relative to the current offset, and "end" means relative to the end. It returns the new offset . 40 | + `chmod(mode) exception `: changes the mode of the file to mode. 41 | + `chown(uid, gid) exception`: changes the mode of the file to mode. 42 | + `chdir() exception`: changes the current working directory to the file. 43 | + `stat() exception => class stat`: get file stat. 44 | + `isDir() exception => Boolean`: if the file is directory. 45 | + `readDir(n:Integer) exception => Array`: reads the contents of the directory associated with the file f and returns a slice of DirEntry values in directory order. Subsequent calls on the same file will yield later DirEntry records in the directory. 46 | + If n > 0, ReadDir returns at most n DirEntry records. In this case, if ReadDir returns an empty slice, it will return an error explaining why. At the end of a directory, the error is io.EOF. 47 | + If n <= 0, ReadDir returns all the DirEntry records remaining in the directory. 48 | + `eof() => bool`: If there is no more data in the file. 49 | -------------------------------------------------------------------------------- /doc/std_os.md: -------------------------------------------------------------------------------- 1 | ## Standard Library - os 2 | 3 | ```python 4 | import os 5 | ``` 6 | 7 | ### Functions 8 | 9 | + `chdir(dir:String) exception`: changes the current working directory to the named directory. 10 | + `exit(code:Integer)`: causes the current program to exit with the given status code. 11 | + `getEnv(key:String) => String`: retrieves the value of the environment variable named by the key. 12 | + `setEnv(key:String,val:String) exception`: sets the value of the environment variable named by the key. 13 | + `args() => Array`: returns command-line arguments, starting with the program name. 14 | + `getegid() => Integer`: returns the numeric effective group id of the caller. 15 | + `geteuid() => Integer`: returns the numeric effective user id of the caller. 16 | + `getgid() => Integer`: returns the numeric group id of the caller. 17 | + `getpid() => Integer`: returns the process id of the caller. 18 | + `getppid() => Integer`: returns the process id of the caller's parent. 19 | + `getuid() => Integer`: returns the numeric user id of the caller. 20 | + `exec(cmd:String, ...args:String) => String`: execute the named program with the given arguments. return the output with protgram. 21 | 22 | -------------------------------------------------------------------------------- /doc/syntax.md: -------------------------------------------------------------------------------- 1 | ## Gscript Syntax 2 | 3 | ### Type 4 | 5 | Following are most frequently used types in gscript: 6 | 7 | ```python 8 | 100 # Number 9 | 100.21 # Number 10 | "gscript" # String 11 | false # Boolean 12 | [1,"gs",{}] # Array 13 | {foo: 1, bar:"bar"} # Objet 14 | func(){} # Closure 15 | nil # nil 16 | ``` 17 | 18 | **Array** 19 | 20 | ```python 21 | let arr = [1,2,[1,2,3]] 22 | arr[0] # 1 23 | arr[2][2] # 3 24 | arr[3] # out of range, will panic 25 | ``` 26 | 27 | **Object** 28 | 29 | ```python 30 | let obj = { 31 | foo:"foo", 32 | bar:{ 33 | 1:"1", 34 | arr:[1,2,nil], 35 | }, 36 | } 37 | 38 | obj["foo"] # "foo" 39 | obj.foo # "foo" 40 | obj.bar[1] # "1" 41 | obj.bar.arr # [1,2,nil] 42 | ``` 43 | 44 | ### Variables and Scopes 45 | 46 | Use keyword `let` to define a new local variable. Using undefined variable will make compile fail: 47 | 48 | ```python 49 | let a = 1 50 | 51 | # enter a new scope 52 | { 53 | print(a) # a==1 54 | let a = 2 55 | let b = 3 56 | print(a) # a==2 57 | print(b) # b==3 58 | } 59 | 60 | print(a) # a==1 61 | print(b) # invalid, complie will not pass 62 | ``` 63 | 64 | Using a variable outside the scope will make compile fail, too. 65 | 66 | Like many other script language, a variable can be assigned to a new value of different type. 67 | 68 | ```python 69 | let a = {} 70 | a = [] # ok 71 | ``` 72 | 73 | We can declare multiple variables using one `let`: 74 | 75 | ```python 76 | let a, b = 1, "hello" # a=1,b="hello" 77 | let a, b = 1 # a=1,b=nil 78 | let a = 1,"hello" # a=1 79 | ``` 80 | 81 | Of course, we can assign to multiple variables use one `=`: 82 | 83 | ```python 84 | let a,b 85 | a, b = 1, 2 86 | ``` 87 | 88 | ### Operators 89 | 90 | **Unary Operators** 91 | 92 | | Operator | Usage | Types | 93 | | :------: | :--------------: | :--------------------: | 94 | | `~` | bitwise negation | Integer | 95 | | `-` | `0 - x` | Number(Integer, Float) | 96 | | `!` | `!x` | all | 97 | | `--` | `--x` or `x--` | Number | 98 | | `++` | `++x` or `x++` | Number | 99 | 100 | Be careful to use `--` and `++`. If these two operators are used as statements: 101 | 102 | ```python 103 | # x++ is the same as ++x 104 | let x = 0 105 | x++ # x==1 106 | ++x # x==2 107 | 108 | # x-- is the same as --x 109 | x-- # x==1 110 | --x # x==0 111 | ``` 112 | 113 | But if these two operators are used as expressions: 114 | 115 | ```python 116 | let x = 0 117 | print(x++) # output: 0 118 | print(++x) # output: 2 119 | print(--x) # output: 1 120 | print(x--) # output: 1 121 | # now x==0 122 | ``` 123 | 124 | The behaviors of `--` and `++` are the same as language `JavaScript` and `C`. 125 | 126 | **Binary Operators** 127 | 128 | | Operator | Usage | Types | 129 | | :------: | :------------: | :------------: | 130 | | `+` | add | String, Number | 131 | | `-` | sub | Number | 132 | | `*` | multiply | Number | 133 | | `/` | divide | Number | 134 | | `//` | integer divide | Number | 135 | | `%` | mod | Integer | 136 | | `&` | bitwise AND | Integer | 137 | | `\|` | bitwise OR | Integer | 138 | | `^` | bitwise XOR | Integer | 139 | | `>>` | shrift right | Integer | 140 | | `<<` | shrift left | Integer | 141 | | `<=` | LE | String, Number | 142 | | `<` | LT | String, Number | 143 | | `>=` | GE | String, Number | 144 | | `>` | GT | String, Number | 145 | | `==` | EQ | String, Number | 146 | | `!=` | NE | String, Number | 147 | | `&&` | logical AND | all | 148 | | `\|\|` | logical OR | all | 149 | | `[]` | `object[key]` | Object | 150 | 151 | *note: output type of (number + string) will be a string.* 152 | 153 | **Ternary Operators** 154 | 155 | ```python 156 | # condition ? true expression : false expression 157 | let result = 1 < 2 ? 1 : 2; 158 | print(result) # 1 159 | ``` 160 | 161 | **Assignment Operators** 162 | 163 | | Operator | Example | Usage | 164 | | :------: | :----------------------: | :---------------------: | 165 | | `=` | `lhs[,lhs] = rhs[,rhs]` | `lhs[,lhs] = rhs[,rhs]` | 166 | | `+=` | `lhs[,lhs] += rhs[,rhs]` | `lhs = lhs + rhs` | 167 | | `-=` | `lhs[,lhs] -= rhs[,rhs]` | `lhs = lhs - rhs` | 168 | | `*=` | `lhs[,lhs] *= rhs[,rhs]` | `lhs = lhs * rhs` | 169 | | `/=` | `lhs[,lhs] /= rhs[,rhs]` | `lhs = lhs / rhs` | 170 | | `%=` | `lhs[,lhs] %= rhs[,rhs]` | `lhs = lhs % rhs` | 171 | | `&=` | `lhs[,lhs] &= rhs[,rhs]` | `lhs = lhs & rhs` | 172 | | `^=` | `lhs[,lhs] ^= rhs[,rhs]` | `lhs = lhs ^ rhs` | 173 | | `\|=` | `lhs[,lhs] \|= rhs[,rhs]` | `lhs = lhs \| rhs` | 174 | 175 | **Operator Precedences** 176 | 177 | | Precedence | Operator | 178 | | :--------: | :---------------: | 179 | | 13 | `++` `--` | 180 | | 12 | `-` `~` `!` | 181 | | 11 | `/` `*` `%` `//` | 182 | | 10 | `+` `-` | 183 | | 9 | `>>` `<<` | 184 | | 8 | `>` `<` `>=` `<=` | 185 | | 7 | `==` `!=` | 186 | | 6 | `&` | 187 | | 5 | `^` | 188 | | 4 | `\|` | 189 | | 3 | `&&` | 190 | | 2 | `\|\|` | 191 | | 1 | `?:` | 192 | 193 | ### Statements 194 | 195 | **If Statement** 196 | 197 | ```python 198 | if (a==1){ 199 | print(1) 200 | } 201 | elif (a==2){ 202 | print(2) 203 | } 204 | else { 205 | print(3) 206 | } 207 | ``` 208 | 209 | Use `{}` to mark a block. If there is only one statement in block, we can omit `{}`。 Code above is the same as following: 210 | 211 | ```python 212 | if (a==1) 213 | print(1) 214 | elif (a==2) 215 | print(2) 216 | else 217 | print(3) 218 | ``` 219 | 220 | **While Statement** 221 | 222 | ```python 223 | while(true){ 224 | # do something 225 | } 226 | ``` 227 | 228 | If there is only one statement in block, we can omit `{}`. 229 | 230 | **For Statement** 231 | 232 | ```python 233 | for(let low,high=0,100;low>` | shrift right | Integer | 140 | | `<<` | shrift left | Integer | 141 | | `<=` | LE | String, Number | 142 | | `<` | LT | String, Number | 143 | | `>=` | GE | String, Number | 144 | | `>` | GT | String, Number | 145 | | `==` | EQ | String, Number | 146 | | `!=` | NE | String, Number | 147 | | `&&` | logical AND | all | 148 | | `\|\|` | logical OR | all | 149 | | `[]` | `object[key]` | Object | 150 | 151 | *注意:字符串和数相加的结果是字符串.* 152 | 153 | **三目操作符** 154 | 155 | ```python 156 | # condition ? true expression : false expression 157 | let result = 1 < 2 ? 1 : 2; 158 | print(result) # 1 159 | ``` 160 | 161 | **赋值操作符** 162 | 163 | | Operator | Example | Usage | 164 | | :------: | :-----------------------: | :---------------------: | 165 | | `=` | `lhs[,lhs] = rhs[,rhs]` | `lhs[,lhs] = rhs[,rhs]` | 166 | | `+=` | `lhs[,lhs] += rhs[,rhs]` | `lhs = lhs + rhs` | 167 | | `-=` | `lhs[,lhs] -= rhs[,rhs]` | `lhs = lhs - rhs` | 168 | | `*=` | `lhs[,lhs] *= rhs[,rhs]` | `lhs = lhs * rhs` | 169 | | `/=` | `lhs[,lhs] /= rhs[,rhs]` | `lhs = lhs / rhs` | 170 | | `%=` | `lhs[,lhs] %= rhs[,rhs]` | `lhs = lhs % rhs` | 171 | | `&=` | `lhs[,lhs] &= rhs[,rhs]` | `lhs = lhs & rhs` | 172 | | `^=` | `lhs[,lhs] ^= rhs[,rhs]` | `lhs = lhs ^ rhs` | 173 | | `\|=` | `lhs[,lhs] \|= rhs[,rhs]` | `lhs = lhs \| rhs` | 174 | 175 | **操作符优先级** 176 | 177 | | Precedence | Operator | 178 | | :--------: | :---------------: | 179 | | 13 | `++` `--` | 180 | | 12 | `-` `~` `!` | 181 | | 11 | `/` `*` `%` `//` | 182 | | 10 | `+` `-` | 183 | | 9 | `>>` `<<` | 184 | | 8 | `>` `<` `>=` `<=` | 185 | | 7 | `==` `!=` | 186 | | 6 | `&` | 187 | | 5 | `^` | 188 | | 4 | `\|` | 189 | | 3 | `&&` | 190 | | 2 | `\|\|` | 191 | | 1 | `?:` | 192 | 193 | ### 语句Statements 194 | 195 | **If语句** 196 | 197 | ```python 198 | if (a==1){ 199 | print(1) 200 | } 201 | elif (a==2){ 202 | print(2) 203 | } 204 | else { 205 | print(3) 206 | } 207 | ``` 208 | 209 | 使用`{}`标记一个块。如果块中只包含一个语句的话,我们能省略掉`{}`。上述的代码和下面的一样: 210 | 211 | ```python 212 | if (a==1) 213 | print(1) 214 | elif (a==2) 215 | print(2) 216 | else 217 | print(3) 218 | ``` 219 | 220 | 这点和`C`语言一致。 221 | 222 | **While语句** 223 | 224 | ```python 225 | while(true){ 226 | # do something 227 | } 228 | ``` 229 | 230 | 同理,块里只有一个语句的话,可省略`{}`。 231 | 232 | **For语句** 233 | 234 | ```python 235 | for(let low,high=0,100;low> 20 | INS_BINARY_SHL // << 21 | INS_BINARY_LE // <= 22 | INS_BINARY_GE // >= 23 | INS_BINARY_LT // < 24 | INS_BINARY_GT // > 25 | INS_BINARY_EQ // == 26 | INS_BINARY_NE // != 27 | INS_BINARY_LAND // && 28 | INS_BINARY_LOR // || 29 | INS_BINARY_ATTR // [] 30 | INS_LOAD_NIL 31 | INS_STORE_KV 32 | INS_PUSH_NAME_NIL 33 | INS_PUSH_NAME 34 | INS_COPY_STACK_TOP 35 | INS_POP_TOP 36 | INS_STOP 37 | INS_ATTR_ASSIGN // = 38 | INS_ATTR_ASSIGN_ADDEQ // += 39 | INS_ATTR_ASSIGN_SUBEQ // -= 40 | INS_ATTR_ASSIGN_MULEQ // *= 41 | INS_ATTR_ASSIGN_DIVEQ // /= 42 | INS_ATTR_ASSIGN_MODEQ // %= 43 | INS_ATTR_ASSIGN_ANDEQ // &= 44 | INS_ATTR_ASSIGN_XOREQ // ^= 45 | INS_ATTR_ASSIGN_OREQ // |= 46 | INS_ATTR_ACCESS 47 | INS_ROT_TWO 48 | INS_EXPORT 49 | INS_END_TRY 50 | INS_NEW_EMPTY_MAP 51 | INS_LOAD_CONST 52 | INS_LOAD_STD_CONST 53 | INS_LOAD_NAME 54 | INS_LOAD_FUNC 55 | INS_LOAD_STD_FUNC 56 | INS_LOAD_BUILTIN 57 | INS_LOAD_ANONYMOUS 58 | INS_LOAD_STD_ANONYMOUS 59 | INS_LOAD_UPVALUE 60 | INS_LOAD_PROTO 61 | INS_LOAD_STDLIB 62 | INS_STORE_NAME 63 | INS_STORE_UPVALUE 64 | INS_RESIZE_NAMETABLE 65 | INS_SLICE_NEW 66 | INS_NEW_MAP 67 | INS_JUMP_REL 68 | INS_JUMP_ABS 69 | INS_JUMP_IF 70 | INS_JUMP_LAND 71 | INS_JUMP_LOR 72 | INS_JUMP_CASE 73 | INS_CALL 74 | INS_RETURN 75 | INS_TRY 76 | ) 77 | -------------------------------------------------------------------------------- /proto/proto.go: -------------------------------------------------------------------------------- 1 | package proto 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "encoding/binary" 7 | "fmt" 8 | "gscript/compiler/ast" 9 | "io" 10 | "math" 11 | "os" 12 | ) 13 | 14 | const ( 15 | magicNumber = 0x00686a6c 16 | VersionMajor byte = 0 17 | VersionMinor byte = 5 18 | ) 19 | 20 | const ( 21 | typeString = 0 22 | typeInteger = 1 23 | typeFloat = 2 24 | typeTrue = 3 25 | typeFalse = 4 26 | typeNil = 5 27 | ) 28 | 29 | type Proto struct { 30 | FilePath string 31 | Consts []interface{} 32 | Funcs []FuncProto 33 | AnonymousFuncs []AnonymousFuncProto 34 | Text []byte 35 | } 36 | 37 | type Header struct { 38 | Magic uint32 39 | VersionMajor byte 40 | VersionMinor byte 41 | } 42 | 43 | func writeHeader(w io.Writer) error { 44 | var buff bytes.Buffer 45 | writeUint32(&buff, magicNumber) 46 | writeVersion(&buff) 47 | _, err := io.Copy(w, &buff) 48 | return err 49 | } 50 | 51 | func readHeader(r *bufio.Reader) (h Header, err error) { 52 | magic, err := readUint32(r) 53 | if err != nil { 54 | return 55 | } 56 | if magic != magicNumber { 57 | return h, fmt.Errorf("invalid magic number") 58 | } 59 | h.Magic = magic 60 | h.VersionMajor, err = r.ReadByte() 61 | if err != nil { 62 | return 63 | } 64 | h.VersionMinor, err = r.ReadByte() 65 | return 66 | } 67 | 68 | func writeVersion(buff *bytes.Buffer) { 69 | buff.WriteByte(VersionMajor) 70 | buff.WriteByte(VersionMinor) 71 | } 72 | 73 | func WriteProtos(w io.Writer, protos []Proto) error { 74 | if err := writeHeader(w); err != nil { 75 | return err 76 | } 77 | var buff bytes.Buffer 78 | writeUint32(&buff, uint32(len(protos))) 79 | for i := range protos { 80 | writeProto(&buff, &protos[i]) 81 | } 82 | _, err := io.Copy(w, &buff) 83 | return err 84 | } 85 | 86 | func WriteProto(w io.Writer, proto *Proto) error { 87 | var buff bytes.Buffer 88 | writeProto(&buff, proto) 89 | _, err := io.Copy(w, &buff) 90 | return err 91 | } 92 | 93 | func writeProto(w *bytes.Buffer, proto *Proto) { 94 | writeString(w, proto.FilePath) 95 | // for i, cons := range proto.Consts { 96 | // fmt.Println(i, cons) 97 | // } 98 | writeConsts(w, proto.Consts) 99 | writeFuncs(w, proto.Funcs) 100 | writeAnonymousFuncs(w, proto.AnonymousFuncs) 101 | writeText(w, proto.Text) 102 | } 103 | 104 | // [funcsCnt:4B] [funcs] 105 | // func: [UpValueCnt:4B] [UpValues] [BasicInfo] 106 | // UpValue: [DirectDependent:1B] [Index:4B] 107 | func writeAnonymousFuncs(w *bytes.Buffer, funcs []AnonymousFuncProto) { 108 | writeUint32(w, uint32(len(funcs))) 109 | for _, _func := range funcs { 110 | writeUint32(w, uint32(len(_func.UpValues))) 111 | for _, upvalue := range _func.UpValues { 112 | if upvalue.DirectDependent { 113 | w.WriteByte(1) 114 | } else { 115 | w.WriteByte(0) 116 | } 117 | writeUint32(w, upvalue.Index) 118 | } 119 | writeBasicInfo(w, _func.Info) 120 | } 121 | } 122 | 123 | func readAnonymousFuncs(r *bufio.Reader) (funcs []AnonymousFuncProto, err error) { 124 | funcCnt, err := readUint32(r) 125 | if err != nil { 126 | return 127 | } 128 | funcs = make([]AnonymousFuncProto, 0, funcCnt) 129 | for i := 0; i < int(funcCnt); i++ { 130 | var _func AnonymousFuncProto 131 | upvalueCnt, err := readUint32(r) 132 | if err != nil { 133 | return nil, err 134 | } 135 | for j := 0; j < int(upvalueCnt); j++ { 136 | var upvalue UpValuePtr 137 | b, err := r.ReadByte() 138 | if err != nil { 139 | return funcs, err 140 | } 141 | upvalue.DirectDependent = b == 1 142 | upvalue.Index, err = readUint32(r) 143 | if err != nil { 144 | return funcs, err 145 | } 146 | _func.UpValues = append(_func.UpValues, upvalue) 147 | } 148 | _func.Info, err = readBasicInfo(r) 149 | if err != nil { 150 | return funcs, err 151 | } 152 | funcs = append(funcs, _func) 153 | } 154 | return 155 | } 156 | 157 | // [funcsCnt:4B] [funcs] 158 | // func: [funcName] [upValueCnt:4B] [UpValues] [BasicInfo] 159 | // UpValue: [uint32:4B] 160 | func writeFuncs(w *bytes.Buffer, funcs []FuncProto) { 161 | writeUint32(w, uint32(len(funcs))) 162 | for _, _func := range funcs { 163 | writeString(w, _func.Name) 164 | writeUint32(w, uint32(len(_func.UpValues))) 165 | for _, upvalue := range _func.UpValues { 166 | writeUint32(w, upvalue) 167 | } 168 | writeBasicInfo(w, _func.Info) 169 | } 170 | } 171 | 172 | func readFunc(r *bufio.Reader) (_func FuncProto, err error) { 173 | _func.Name, err = readString(r) 174 | if err != nil { 175 | return 176 | } 177 | upvalueCnt, err := readUint32(r) 178 | if err != nil { 179 | return 180 | } 181 | for i := 0; i < int(upvalueCnt); i++ { 182 | idx, err := readUint32(r) 183 | if err != nil { 184 | return _func, err 185 | } 186 | _func.UpValues = append(_func.UpValues, idx) 187 | } 188 | _func.Info, err = readBasicInfo(r) 189 | return 190 | } 191 | 192 | func readFuncs(r *bufio.Reader) (funcs []FuncProto, err error) { 193 | funcCnt, err := readUint32(r) 194 | if err != nil { 195 | return nil, err 196 | } 197 | funcs = make([]FuncProto, 0, funcCnt) 198 | for i := 0; i < int(funcCnt); i++ { 199 | _func, err := readFunc(r) 200 | if err != nil { 201 | return nil, err 202 | } 203 | funcs = append(funcs, _func) 204 | } 205 | return 206 | } 207 | 208 | // BaiscInfo: [VaArgs:1B] [parametersCnt:4B] [parameters] [textLen:4B] [text:[]byte] 209 | // parameter: [nameLength:4B] [name:string] [Default:consts] 210 | func writeBasicInfo(w *bytes.Buffer, info *BasicInfo) { 211 | if info.VaArgs { 212 | w.WriteByte(1) 213 | } else { 214 | w.WriteByte(0) 215 | } 216 | writeUint32(w, uint32(len(info.Parameters))) 217 | for _, par := range info.Parameters { 218 | writeString(w, par.Name) 219 | writeConst(w, par.Default) 220 | } 221 | writeText(w, info.Text) 222 | } 223 | 224 | func readBasicInfo(r *bufio.Reader) (info *BasicInfo, err error) { 225 | VaArgs, err := r.ReadByte() 226 | if err != nil { 227 | return nil, err 228 | } 229 | info = new(BasicInfo) 230 | info.VaArgs = VaArgs == 1 231 | parCnt, err := readUint32(r) 232 | if err != nil { 233 | return nil, err 234 | } 235 | for i := 0; i < int(parCnt); i++ { 236 | name, err := readString(r) 237 | if err != nil { 238 | return nil, err 239 | } 240 | c, err := readConst(r) 241 | if err != nil { 242 | return nil, err 243 | } 244 | info.Parameters = append(info.Parameters, ast.Parameter{ 245 | Name: name, 246 | Default: c, 247 | }) 248 | } 249 | info.Text, err = readText(r) 250 | return 251 | } 252 | 253 | func writeText(w *bytes.Buffer, text []byte) { 254 | writeUint32(w, uint32(len(text))) 255 | w.Write(text) 256 | } 257 | 258 | func readText(r *bufio.Reader) ([]byte, error) { 259 | length, err := readUint32(r) 260 | if err != nil { 261 | return nil, err 262 | } 263 | text := make([]byte, length) 264 | _, err = io.ReadFull(r, text) 265 | return text, err 266 | } 267 | 268 | // Write count(4B) of constants at first, then write all constants into w, 269 | // every kind of constant will be organized like following: 270 | // string: [type:1B](0) [length:4B] [values:len(string)] 271 | // int64: [type:1B](1) [values:8B] 272 | // float64:[type:1B](2) [values:8B] 273 | // true: [type:1B](3) 274 | // false: [type:1B](4) 275 | func writeConsts(w *bytes.Buffer, consts []interface{}) { 276 | writeUint32(w, uint32(len(consts))) 277 | for _, constant := range consts { 278 | writeConst(w, constant) 279 | } 280 | } 281 | 282 | func readConsts(r *bufio.Reader) (consts []interface{}, err error) { 283 | length, err := readUint32(r) 284 | if err != nil { 285 | return 286 | } 287 | consts = make([]interface{}, 0, length) 288 | for i := 0; i < int(length); i++ { 289 | constant, err := readConst(r) 290 | if err != nil { 291 | return nil, err 292 | } 293 | consts = append(consts, constant) 294 | } 295 | return 296 | } 297 | 298 | func writeConst(w *bytes.Buffer, constant interface{}) { 299 | if constant == nil { 300 | w.WriteByte(typeNil) 301 | return 302 | } 303 | switch val := constant.(type) { 304 | case string: 305 | w.WriteByte(typeString) 306 | writeString(w, val) 307 | case int64: 308 | w.WriteByte(typeInteger) 309 | writeUint64(w, uint64(val)) 310 | case float64: 311 | w.WriteByte(typeFloat) 312 | writeFloat64(w, val) 313 | case bool: 314 | if val { 315 | w.WriteByte(typeTrue) 316 | } else { 317 | w.WriteByte(typeFalse) 318 | } 319 | default: 320 | fmt.Printf("writing invalid constant type\n") 321 | os.Exit(0) 322 | } 323 | } 324 | 325 | func readConst(r *bufio.Reader) (interface{}, error) { 326 | t, err := r.ReadByte() 327 | if err != nil { 328 | return nil, err 329 | } 330 | switch t { 331 | case typeString: 332 | str, err := readString(r) 333 | return str, err 334 | case typeInteger: 335 | num, err := readUint64(r) 336 | return int64(num), err 337 | case typeFloat: 338 | f, err := readFloat64(r) 339 | return f, err 340 | case typeTrue: 341 | return true, nil 342 | case typeFalse: 343 | return false, nil 344 | case typeNil: 345 | return nil, nil 346 | default: 347 | return nil, fmt.Errorf("reading invalid constant type") 348 | } 349 | } 350 | 351 | func writeFloat64(w *bytes.Buffer, src float64) { 352 | writeUint64(w, math.Float64bits(src)) 353 | } 354 | 355 | func readFloat64(r *bufio.Reader) (float64, error) { 356 | u64, err := readUint64(r) 357 | if err != nil { 358 | return 0, err 359 | } 360 | return math.Float64frombits(u64), nil 361 | } 362 | 363 | func writeUint64(w *bytes.Buffer, src uint64) { 364 | data := make([]byte, 8) 365 | binary.LittleEndian.PutUint64(data, src) 366 | w.Write(data) 367 | } 368 | 369 | func readUint64(r *bufio.Reader) (uint64, error) { 370 | buf := make([]byte, 8) 371 | _, err := io.ReadFull(r, buf) 372 | if err != nil { 373 | return 0, err 374 | } 375 | return binary.LittleEndian.Uint64(buf), nil 376 | } 377 | 378 | func writeUint32(w *bytes.Buffer, src uint32) { 379 | data := make([]byte, 4) 380 | binary.LittleEndian.PutUint32(data, src) 381 | w.Write(data) 382 | } 383 | 384 | func readUint32(r *bufio.Reader) (uint32, error) { 385 | data := make([]byte, 4) 386 | _, err := io.ReadFull(r, data) 387 | if err != nil { 388 | return 0, err 389 | } 390 | v := binary.LittleEndian.Uint32(data) 391 | return v, nil 392 | } 393 | 394 | // length:4B data:string 395 | func writeString(w *bytes.Buffer, src string) { 396 | writeUint32(w, uint32(len(src))) 397 | w.WriteString(src) 398 | } 399 | 400 | func readString(r *bufio.Reader) (string, error) { 401 | length, err := readUint32(r) 402 | if err != nil { 403 | return "", nil 404 | } 405 | data := make([]byte, length) 406 | if _, err := io.ReadFull(r, data); err != nil { 407 | return "", nil 408 | } 409 | return string(data), nil 410 | } 411 | 412 | func ReadProtos(r io.Reader) (h Header, protos []Proto, err error) { 413 | bufr := bufio.NewReader(r) 414 | h, err = readHeader(bufr) 415 | if err != nil { 416 | return 417 | } 418 | protos, err = readProtos(bufr) 419 | return 420 | } 421 | 422 | func ReadProto(r *bufio.Reader) (p Proto, err error) { 423 | p.FilePath, err = readString(r) 424 | if err != nil { 425 | return 426 | } 427 | p.Consts, err = readConsts(r) 428 | if err != nil { 429 | return 430 | } 431 | p.Funcs, err = readFuncs(r) 432 | if err != nil { 433 | return 434 | } 435 | p.AnonymousFuncs, err = readAnonymousFuncs(r) 436 | if err != nil { 437 | return 438 | } 439 | p.Text, err = readText(r) 440 | return 441 | } 442 | 443 | func readProtos(r *bufio.Reader) (protos []Proto, err error) { 444 | protoCnt, err := readUint32(r) 445 | if err != nil { 446 | return 447 | } 448 | protos = make([]Proto, 0, protoCnt) 449 | for i := 0; i < int(protoCnt); i++ { 450 | proto, err := ReadProto(r) 451 | if err != nil { 452 | return nil, err 453 | } 454 | protos = append(protos, proto) 455 | } 456 | return 457 | } 458 | 459 | func WriteProtosToFile(src string, protos []Proto) error { 460 | file, err := os.OpenFile(src, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0664) 461 | if err != nil { 462 | return err 463 | } 464 | defer file.Close() 465 | return WriteProtos(file, protos) 466 | } 467 | 468 | func IsProtoFile(path string) bool { 469 | file, err := os.Open(path) 470 | if err != nil { 471 | return false 472 | } 473 | defer file.Close() 474 | stat, err := file.Stat() 475 | if err != nil { 476 | return false 477 | } 478 | if stat.Size() < 4 { 479 | return false 480 | } 481 | buf := make([]byte, 4) 482 | if _, err := io.ReadFull(file, buf); err != nil { 483 | return false 484 | } 485 | magic := binary.LittleEndian.Uint32(buf) 486 | return magic == magicNumber 487 | } 488 | 489 | func ReadProtosFromFile(src string) (Header, []Proto, error) { 490 | file, err := os.Open(src) 491 | if err != nil { 492 | return Header{}, nil, err 493 | } 494 | defer file.Close() 495 | return ReadProtos(file) 496 | } 497 | -------------------------------------------------------------------------------- /std/Buffer.gs: -------------------------------------------------------------------------------- 1 | class Buffer{ 2 | __self(cap, str) { 3 | if (cap == nil) return; 4 | if (str != nil) this._buffer = __buffer_from(str); 5 | else this._buffer = __buffer_new(cap); 6 | this._cap = cap; 7 | } 8 | cap() { 9 | return this._cap; 10 | } 11 | toString(offset=0, length=-1) { 12 | if (length == -1) 13 | length = this._cap - offset; 14 | return __buffer_toString(this._buffer, offset, length); 15 | } 16 | slice(offset, length) { 17 | if (length == nil) 18 | length = len(this._buffer) - offset; 19 | let buf = new Buffer; 20 | buf._buffer = __buffer_slice(this._buffer, offset, length); 21 | buf._cap = length; 22 | return buf; 23 | } 24 | readInt8(offset) { 25 | return __buffer_readNumber(this._buffer, offset, 1, true, false, false); 26 | } 27 | readUint8(offset) { 28 | return __buffer_readNumber(this._buffer, offset, 1, false, false, false); 29 | } 30 | readInt16BE(offset) { 31 | return __buffer_readNumber(this._buffer, offset, 2, true, false, false); 32 | } 33 | readInt16LE(offset) { 34 | return __buffer_readNumber(this._buffer, offset, 2, true, true, false); 35 | } 36 | readUint16BE(offset) { 37 | return __buffer_readNumber(this._buffer, offset, 2, false, false, false); 38 | } 39 | readUint16LE(offset) { 40 | return __buffer_readNumber(this._buffer, offset, 2, false, true, false); 41 | } 42 | readInt32BE(offset) { 43 | return __buffer_readNumber(this._buffer, offset, 4, true, false, false); 44 | } 45 | readInt32LE(offset) { 46 | return __buffer_readNumber(this._buffer, offset, 4, true, true, false); 47 | } 48 | readUint32BE(offset) { 49 | return __buffer_readNumber(this._buffer, offset, 4, false, false, false); 50 | } 51 | readUint32LE(offset) { 52 | return __buffer_readNumber(this._buffer, offset, 4, false, true, false); 53 | } 54 | readInt64BE(offset) { 55 | return __buffer_readNumber(this._buffer, offset, 8, true, false, false); 56 | } 57 | readInt64LE(offset) { 58 | return __buffer_readNumber(this._buffer, offset, 8, true, true, false); 59 | } 60 | readUint64BE(offset) { 61 | return __buffer_readNumber(this._buffer, offset, 8, false, false, false); 62 | } 63 | readUint64LE(offset) { 64 | return __buffer_readNumber(this._buffer, offset, 8, false, true, false); 65 | } 66 | readFloat32LE(offset) { 67 | return __buffer_readNumber(this._buffer, offset, 4, false, true, true); 68 | } 69 | readFloat32BE(offset) { 70 | return __buffer_readNumber(this._buffer, offset, 4, false, false, true); 71 | } 72 | readFloat64LE(offset) { 73 | return __buffer_readNumber(this._buffer, offset, 8, false, true, true); 74 | } 75 | readFloat64BE(offset) { 76 | return __buffer_readNumber(this._buffer, offset, 8, false, false, true); 77 | } 78 | write8(offset, number) { 79 | __buffer_writeNumber(this._buffer, offset, 1, false, number); 80 | } 81 | write16BE(offset, number) { 82 | __buffer_writeNumber(this._buffer, offset, 2, false, number); 83 | } 84 | write16LE(offset, number) { 85 | __buffer_writeNumber(this._buffer, offset, 2, true, number); 86 | } 87 | write32BE(offset, number) { 88 | __buffer_writeNumber(this._buffer, offset, 4, false, number); 89 | } 90 | write32LE(offset, number) { 91 | __buffer_writeNumber(this._buffer, offset, 4, true, number); 92 | } 93 | write64BE(offset, number) { 94 | __buffer_writeNumber(this._buffer, offset, 8, false, number); 95 | } 96 | write64LE(offset, number) { 97 | __buffer_writeNumber(this._buffer, offset, 8, true, number); 98 | } 99 | } 100 | 101 | export { 102 | alloc: func(cap) { 103 | return new Buffer(cap); 104 | }, 105 | from: func(str) { 106 | return new Buffer(len(str),str); 107 | }, 108 | concat: func(buf1, buf2) { 109 | let buf = new Buffer; 110 | buf._buffer = __buffer_concat(buf1._buffer, buf2._buffer); 111 | buf._cap = buf1.cap() + buf2.cap(); 112 | return buf; 113 | }, 114 | copy: func(buf1, buf2, length, start1=0, start2=0) { 115 | __buffer_copy(buf1._buffer, buf2._buffer, length, start1, start2); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /std/embed.go: -------------------------------------------------------------------------------- 1 | package std 2 | 3 | import ( 4 | "bufio" 5 | "embed" 6 | "fmt" 7 | "gscript/proto" 8 | ) 9 | 10 | //go:embed *.gsproto 11 | var ProtoFiles embed.FS 12 | 13 | const ProtoSuffix string = ".gsproto" 14 | 15 | var StdLibMap = map[string]uint32{ 16 | "Buffer": 0, 17 | "fs": 1, 18 | "os": 2, 19 | } 20 | 21 | var stdLibs = []string{"Buffer", "fs", "os"} 22 | 23 | func GetLibNameByProtoNum(num uint32) string { 24 | return stdLibs[num] 25 | } 26 | 27 | func GetLibProtoNumByName(name string) (uint32, error) { 28 | num, ok := StdLibMap[name] 29 | if !ok { 30 | return 0, fmt.Errorf("invalid std libarary: %s", name) 31 | } 32 | return num, nil 33 | } 34 | 35 | func ReadProto(lib string) (proto.Proto, error) { 36 | file, err := ProtoFiles.Open(lib + ProtoSuffix) 37 | if err != nil { 38 | return proto.Proto{}, fmt.Errorf("can not find std libarary bytes code: %v", err) 39 | } 40 | defer file.Close() 41 | return proto.ReadProto(bufio.NewReader(file)) 42 | } 43 | 44 | func ReadProtos() ([]proto.Proto, error) { 45 | protos := make([]proto.Proto, len(StdLibMap)) 46 | for lib, num := range StdLibMap { 47 | p, err := ReadProto(lib) 48 | if err != nil { 49 | return nil, err 50 | } 51 | protos[num] = p 52 | } 53 | return protos, nil 54 | } 55 | -------------------------------------------------------------------------------- /std/fs.gs: -------------------------------------------------------------------------------- 1 | import Buffer; 2 | 3 | class File{ 4 | __self(file) { 5 | this._file = file; 6 | this._eof = false; 7 | } 8 | read(buf,size) { 9 | if (this._eof) return 0; 10 | if(size == nil) size = buf.cap(); 11 | let n = __read(this._file, buf._buffer, size); 12 | this._eof = n == 0; 13 | return n; 14 | } 15 | # data is a Buffer or String 16 | write(data, size=-1) { 17 | if (type(data) != "String") { 18 | data = data._buffer; 19 | } 20 | size = size == -1 ? len(data) : size; 21 | return __write(this._file, data, size); 22 | } 23 | close() { 24 | __close(this._file); 25 | } 26 | # whence = cur, end or start 27 | seek(offset, whence="cur") { 28 | let n = __seek(this._file, offset, whence); 29 | this._eof = false; 30 | return n; 31 | } 32 | chmod(mode) { 33 | __fchmod(this._file, mode); 34 | } 35 | chown(uid, gid) { 36 | __fchown(this._file, uid, gid); 37 | } 38 | chdir() { 39 | __fchdir(this._file); 40 | } 41 | stat() { 42 | if (this._stat == nil) 43 | this._stat = __fstat(this._file); 44 | return this._stat; 45 | } 46 | isDir() { 47 | if (this._stat == nil) 48 | this.stat(); 49 | return this._stat.is_dir; 50 | } 51 | readDir(n) { 52 | return __freaddir(this._file, n); 53 | } 54 | # return bool 55 | eof() { 56 | return this._eof; 57 | } 58 | } 59 | 60 | export { 61 | open: func(path, flag="r", mode=0664) { 62 | let file = __open(path, flag, mode); 63 | return new File(file); 64 | }, 65 | create: func(path, mode=0664) { 66 | let file = __open(path, "crw", mode); 67 | return new File(file); 68 | }, 69 | stat: func(path) { 70 | return __stat(path); 71 | }, 72 | remove: func(path) { 73 | __remove(path); 74 | }, 75 | readFile: func(path) { 76 | let size = __stat(path).size; 77 | let buf = Buffer.alloc(size); 78 | let file = __open(path, "r", 0); 79 | let n = __read(file, buf._buffer, size); 80 | __close(file); 81 | return buf, n; 82 | }, 83 | mkdir: func(dir, mode=0664) { 84 | __mkdir(dir, mode); 85 | }, 86 | chmod: func(path, mode) { 87 | __chmod(path, mode); 88 | }, 89 | chown: func(path, uid, gid) { 90 | __chown(path, uid, gid); 91 | }, 92 | rename: func(oldpath, newpath) { 93 | __rename(oldpath, newpath); 94 | }, 95 | readDir: func(path) { 96 | return __readdir(path); 97 | }, 98 | } -------------------------------------------------------------------------------- /std/os.gs: -------------------------------------------------------------------------------- 1 | export { 2 | chdir: func(path) { 3 | __chdir(path); 4 | }, 5 | exit: func(code) { 6 | __exit(code); 7 | }, 8 | getEnv: func(key) { 9 | return __getenv(key); 10 | }, 11 | setEnv: func(key, val) { 12 | return __setenv(key, val); 13 | }, 14 | args: func() { 15 | return __args(); 16 | }, 17 | getegid: func() { 18 | return __getegid(); 19 | }, 20 | geteuid: func() { 21 | return __geteuid(); 22 | }, 23 | getgid: func() { 24 | return __getgid(); 25 | }, 26 | getpid: func() { 27 | return __getpid(); 28 | }, 29 | getppid: func() { 30 | return __getppid(); 31 | }, 32 | getuid: func() { 33 | return __getuid(); 34 | }, 35 | exec: func(cmd,...args) { 36 | return __exec(cmd, args); 37 | }, 38 | } -------------------------------------------------------------------------------- /test.gs: -------------------------------------------------------------------------------- 1 | import fs; 2 | import os; 3 | 4 | let filepath = "./text.txt" 5 | 6 | try{ 7 | # open, create and truncate text.txt 8 | let file = fs.open(filepath,"wct"); 9 | 10 | # write some message into file 11 | file.write("hello world!\n") 12 | file.write("gscript is a good language!") 13 | 14 | # close file 15 | file.close(); 16 | let stat = fs.stat(filepath) 17 | print("size of text.txt is " + stat.size + "B"); 18 | # read all data in text.txt 19 | let data = fs.readFile(filepath); 20 | print("message of", filepath, "is:") 21 | print(data.toString()) 22 | 23 | # remove text.txt 24 | fs.remove(filepath) 25 | print("success!") 26 | } 27 | catch(e){ 28 | print("operation failed, error msg:",e) 29 | os.exit(0) 30 | } 31 | -------------------------------------------------------------------------------- /util/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "gscript/compiler/codegen" 6 | "gscript/compiler/lexer" 7 | "gscript/compiler/parser" 8 | "gscript/proto" 9 | "gscript/std" 10 | "io/ioutil" 11 | "os" 12 | "path" 13 | ) 14 | 15 | func main() { 16 | codegen.SetStdLibGenMode() 17 | if len(os.Args) == 1 { 18 | fmt.Printf("Usage: %s \n", os.Args[0]) 19 | return 20 | } 21 | for lib := range std.StdLibMap { 22 | err := complieStdLib(lib) 23 | if err != nil { 24 | fmt.Printf("complie std libarary failed, error message: %v\n", err) 25 | return 26 | } 27 | } 28 | } 29 | 30 | func complieStdLib(stdlib string) error { 31 | source, target := path.Join(os.Args[1], stdlib+".gs"), path.Join(os.Args[1], stdlib+".gsproto") 32 | protoNum := std.StdLibMap[stdlib] 33 | 34 | code, err := ioutil.ReadFile(source) 35 | if err != nil { 36 | return err 37 | } 38 | parser := parser.NewParser(lexer.NewLexer(source, code)) 39 | prog := parser.Parse() 40 | var imports []codegen.Import 41 | for _, _import := range prog.Imports { 42 | for _, lib := range _import.Libs { 43 | num, ok := std.StdLibMap[lib.Path] 44 | if !ok { 45 | return fmt.Errorf("invalid std libarary: %s", lib.Path) 46 | } 47 | if lib.Alias == "" { 48 | lib.Alias = lib.Path 49 | } 50 | imports = append(imports, codegen.Import{ 51 | StdLib: lib.Stdlib, 52 | Alias: lib.Alias, 53 | ProtoNumber: num, 54 | }) 55 | } 56 | } 57 | _proto := codegen.Gen(parser, prog, imports, protoNum) 58 | _proto.FilePath = stdlib 59 | 60 | file, err := os.OpenFile(target, os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0664) 61 | if err != nil { 62 | return err 63 | } 64 | defer file.Close() 65 | return proto.WriteProto(file, &_proto) 66 | } 67 | -------------------------------------------------------------------------------- /vm/debug.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | import ( 4 | "bufio" 5 | "encoding/binary" 6 | "fmt" 7 | "gscript/proto" 8 | "gscript/vm/types" 9 | "os" 10 | "strconv" 11 | "strings" 12 | ) 13 | 14 | var cmds = map[string]func(vm *VM, args []string){ 15 | "help": debugHelp, 16 | "n": debugNext, 17 | "next": debugNext, 18 | "v": debugShowVar, 19 | "var": debugShowVar, 20 | "s": debugShowStack, 21 | "stack": debugShowStack, 22 | "c": debugShowCode, 23 | "code": debugShowCode, 24 | "const": debugShowConstant, 25 | "r": debugRun, 26 | "f": debugShowFunc, 27 | "ff": debugShowAnonymousFunc, 28 | "upvalue": debugShowUpValue, 29 | } 30 | 31 | func debugShowUpValue(vm *VM, args []string) { 32 | fmt.Printf("upValues: ") 33 | for i, v := range vm.curProto.frame.upValues { 34 | fmt.Printf("%v", v.Value) 35 | if i != len(vm.curProto.frame.upValues) { 36 | fmt.Printf(", ") 37 | } 38 | } 39 | fmt.Println() 40 | } 41 | 42 | func showFunc(upValues []*types.GsValue) { 43 | fmt.Printf("upvalues: [") 44 | for i, upValue := range upValues { 45 | showValue(upValue.Value) 46 | if i != len(upValues)-1 { 47 | fmt.Printf(", ") 48 | } 49 | } 50 | fmt.Printf("]\n") 51 | } 52 | 53 | func debugShowAnonymousFunc(vm *VM, args []string) { 54 | if len(args) == 0 { 55 | return 56 | } 57 | num, err := strconv.Atoi(args[0]) 58 | if err != nil { 59 | return 60 | } 61 | f := vm.curProto.anonymousTable[num] 62 | cnt := -1 63 | if len(args) > 1 { 64 | fmt.Scanf("%d", &cnt) 65 | } 66 | showCode(vm, f.Info.Text, 0, cnt) 67 | } 68 | 69 | func debugShowFunc(vm *VM, args []string) { 70 | if len(args) != 0 { 71 | num, err := strconv.Atoi(args[0]) 72 | if err != nil || num >= len(vm.curProto.funcTable) { 73 | return 74 | } 75 | f := vm.curProto.funcTable[num] 76 | upValues, _ := f.UpValueTable.([]*types.GsValue) 77 | showFunc(upValues) 78 | cnt := -1 79 | if len(args) > 1 { 80 | fmt.Scanf("%d", &cnt) 81 | } 82 | showCode(vm, f.Info.Text, 0, cnt) 83 | return 84 | } 85 | for i, f := range vm.curProto.funcTable { 86 | fmt.Printf("%dth: ", i) 87 | upValues, _ := f.UpValueTable.([]*types.GsValue) 88 | showFunc(upValues) 89 | } 90 | fmt.Println() 91 | } 92 | 93 | func debugShowConstant(vm *VM, args []string) { 94 | fmt.Printf("constants: ") 95 | if len(args) == 0 { 96 | return 97 | } 98 | var num int 99 | fmt.Sscanf(args[0], "%d", &num) 100 | for i, value := range vm.protos[num].Consts { 101 | showValue(value) 102 | if i != len(vm.curProto.frame.symbolTable.values)-1 { 103 | fmt.Printf(", ") 104 | } 105 | } 106 | fmt.Println() 107 | } 108 | 109 | func debugRun(vm *VM, args []string) { 110 | for { 111 | if vm.stopped { 112 | return 113 | } 114 | instruction := vm.curProto.frame.text[vm.curProto.frame.pc] 115 | if instruction == proto.INS_STOP { 116 | break 117 | } 118 | vm.curProto.frame.pc++ 119 | Execute(vm, instruction) 120 | } 121 | } 122 | 123 | func debugShowCode(vm *VM, args []string) { 124 | cnt := 15 125 | start := vm.curProto.frame.pc 126 | if len(args) > 0 { 127 | fmt.Sscanf(args[0], "%d", &start) 128 | } 129 | if len(args) > 1 { 130 | fmt.Sscanf(args[1], "%d", &cnt) 131 | } 132 | showCode(vm, vm.curProto.frame.text, start, cnt) 133 | } 134 | 135 | func showCode(vm *VM, text []byte, pc uint32, cnt int) { 136 | for i := 0; ; i++ { 137 | if int(pc) >= len(text) || i == cnt { 138 | break 139 | } 140 | pc += showInstruction(vm, text, pc) 141 | } 142 | } 143 | 144 | func debugShowStack(vm *VM, args []string) { 145 | fmt.Printf("stack: ") 146 | for i := 0; i < len(vm.curProto.stack.Buf); i++ { 147 | val := vm.curProto.stack.Buf[i] 148 | showValue(val) 149 | fmt.Printf(" ") 150 | } 151 | fmt.Println() 152 | } 153 | 154 | func debugShowVar(vm *VM, args []string) { 155 | fmt.Printf("variables: ") 156 | for i, val := range vm.curProto.frame.symbolTable.values { 157 | showValue(val.Value) 158 | if i != len(vm.curProto.frame.symbolTable.values)-1 { 159 | fmt.Printf(", ") 160 | } 161 | } 162 | fmt.Println() 163 | } 164 | 165 | func debugNext(vm *VM, args []string) { 166 | instruction := vm.curProto.frame.text[vm.curProto.frame.pc] 167 | vm.curProto.frame.pc++ 168 | Execute(vm, instruction) 169 | } 170 | 171 | func debugHelp(vm *VM, args []string) { 172 | // TODO 173 | fmt.Println() 174 | } 175 | 176 | func (vm *VM) Debug() { 177 | for { 178 | if vm.stopped { 179 | fmt.Println("done!") 180 | break 181 | } 182 | bufr := bufio.NewReader(os.Stdin) 183 | 184 | line, _, _ := bufr.ReadLine() 185 | args := strings.Split(string(line), " ") 186 | if len(args) == 0 { 187 | continue 188 | } 189 | for i := range args { 190 | args[i] = strings.TrimSpace(args[i]) 191 | } 192 | handler, ok := cmds[args[0]] 193 | if !ok { 194 | continue 195 | } 196 | handler(vm, args[1:]) 197 | fmt.Println() 198 | } 199 | } 200 | 201 | func showValue(val interface{}) { 202 | fprint(os.Stdout, val) 203 | } 204 | 205 | func showInstruction(vm *VM, text []byte, pc uint32) uint32 { 206 | skip := 1 207 | ins := byte(text[pc]) 208 | fmt.Printf("%d \t", pc) 209 | switch ins { 210 | case proto.INS_UNARY_NOT: 211 | fmt.Printf("NOT") 212 | case proto.INS_UNARY_NEG: 213 | fmt.Printf("NEG") 214 | case proto.INS_UNARY_LNOT: 215 | fmt.Printf("LNOT") 216 | case proto.INS_BINARY_ADD: 217 | fmt.Printf("ADD") 218 | case proto.INS_BINARY_SUB: 219 | fmt.Printf("SUB") 220 | case proto.INS_BINARY_MUL: 221 | fmt.Printf("MUL") 222 | case proto.INS_BINARY_DIV: 223 | fmt.Printf("DIV") 224 | case proto.INS_BINARY_MOD: 225 | fmt.Printf("MOD") 226 | case proto.INS_BINARY_AND: 227 | fmt.Printf("AND") 228 | case proto.INS_BINARY_XOR: 229 | fmt.Printf("XOR") 230 | case proto.INS_BINARY_OR: 231 | fmt.Printf("OR") 232 | case proto.INS_BINARY_IDIV: 233 | fmt.Printf("IDIV") 234 | case proto.INS_BINARY_SHR: 235 | fmt.Printf("SHR") 236 | case proto.INS_BINARY_SHL: 237 | fmt.Printf("SHL") 238 | case proto.INS_BINARY_LE: 239 | fmt.Printf("LE") 240 | case proto.INS_BINARY_GE: 241 | fmt.Printf("GE") 242 | case proto.INS_BINARY_LT: 243 | fmt.Printf("LT") 244 | case proto.INS_BINARY_GT: 245 | fmt.Printf("GT") 246 | case proto.INS_BINARY_EQ: 247 | fmt.Printf("EQ") 248 | case proto.INS_BINARY_NE: 249 | fmt.Printf("NE") 250 | case proto.INS_BINARY_LAND: 251 | fmt.Printf("LAND") 252 | case proto.INS_BINARY_LOR: 253 | fmt.Printf("LOR") 254 | case proto.INS_BINARY_ATTR: 255 | fmt.Printf("ATTR") 256 | case proto.INS_LOAD_NIL: 257 | fmt.Printf("LOAD_NIL") 258 | case proto.INS_LOAD_CONST: 259 | pc++ 260 | protoNum := getOpNum(text, pc) 261 | pc += 4 262 | constNum := getOpNum(text, pc) 263 | fmt.Printf("LOAD_CONST %v", vm.protos[protoNum].Consts[constNum]) 264 | skip += 8 265 | case proto.INS_LOAD_STD_CONST: 266 | pc++ 267 | protoNum := getOpNum(text, pc) 268 | pc += 4 269 | constNum := getOpNum(text, pc) 270 | fmt.Printf("LOAD_STD_CONST %v", vm.stdlibs[protoNum].Consts[constNum]) 271 | skip += 8 272 | case proto.INS_LOAD_NAME: 273 | pc++ 274 | fmt.Printf("LOAD_NAME %d", getOpNum(text, pc)) 275 | skip += 4 276 | case proto.INS_LOAD_FUNC: 277 | pc++ 278 | protoNum := getOpNum(text, pc) 279 | pc += 4 280 | fmt.Printf("LOAD_FUNC %d %d", protoNum, getOpNum(text, pc)) 281 | skip += 8 282 | case proto.INS_LOAD_STD_FUNC: 283 | pc++ 284 | protoNum := getOpNum(text, pc) 285 | pc += 4 286 | fmt.Printf("LOAD_STD_FUNC %d %d", protoNum, getOpNum(text, pc)) 287 | skip += 8 288 | case proto.INS_LOAD_ANONYMOUS: 289 | pc++ 290 | protoNum := getOpNum(text, pc) 291 | pc += 4 292 | fmt.Printf("LOAD_ANONYMOUS %d %d", protoNum, getOpNum(text, pc)) 293 | skip += 8 294 | case proto.INS_LOAD_STD_ANONYMOUS: 295 | pc++ 296 | protoNum := getOpNum(text, pc) 297 | pc += 4 298 | fmt.Printf("LOAD_STD_ANONYMOUS %d %d", protoNum, getOpNum(text, pc)) 299 | skip += 8 300 | case proto.INS_LOAD_UPVALUE: 301 | pc++ 302 | fmt.Printf("LOAD_UPVALUE %d", getOpNum(text, pc)) 303 | skip += 4 304 | case proto.INS_LOAD_PROTO: 305 | pc++ 306 | fmt.Printf("LOAD_PROTO %d", getOpNum(text, pc)) 307 | skip += 4 308 | case proto.INS_LOAD_STDLIB: 309 | pc++ 310 | fmt.Printf("LOAD_STDLIB %d", getOpNum(text, pc)) 311 | skip += 4 312 | case proto.INS_STORE_NAME: 313 | pc++ 314 | fmt.Printf("STORE_NAME %d", getOpNum(text, pc)) 315 | skip += 4 316 | case proto.INS_STORE_UPVALUE: 317 | pc++ 318 | fmt.Printf("STORE_UPVALUE %d", getOpNum(text, pc)) 319 | skip += 4 320 | case proto.INS_LOAD_BUILTIN: 321 | pc++ 322 | fmt.Printf("LOAD_BUILTIN \"%s\"", builtinFuncs[getOpNum(text, pc)].name) 323 | skip += 4 324 | case proto.INS_STORE_KV: 325 | fmt.Printf("STORE_KV") 326 | case proto.INS_PUSH_NAME_NIL: 327 | fmt.Printf("PUSH_NAME_NIL") 328 | case proto.INS_CALL: 329 | pc++ 330 | wantRtnCnt := byte(vm.curProto.frame.text[pc]) 331 | pc++ 332 | argCnt := byte(vm.curProto.frame.text[pc]) 333 | fmt.Printf("CALL %d %d", wantRtnCnt, argCnt) 334 | skip += 2 335 | case proto.INS_RETURN: 336 | pc++ 337 | fmt.Printf("RETURN with %d values", getOpNum(text, pc)) 338 | skip += 4 339 | case proto.INS_PUSH_NAME: 340 | fmt.Printf("PUSH_NAME") 341 | case proto.INS_COPY_STACK_TOP: 342 | fmt.Printf("COPY_NAME") 343 | case proto.INS_RESIZE_NAMETABLE: 344 | pc++ 345 | fmt.Printf("RESIZE_NAMETABLE %d", getOpNum(text, pc)) 346 | skip += 4 347 | case proto.INS_POP_TOP: 348 | fmt.Printf("POP_TOP") 349 | case proto.INS_STOP: 350 | fmt.Printf("STOP") 351 | case proto.INS_SLICE_NEW: 352 | pc++ 353 | fmt.Printf("SLICE_NEW %d", getOpNum(text, pc)) 354 | skip += 4 355 | case proto.INS_NEW_EMPTY_MAP: 356 | fmt.Printf("NEW_EMPTY_MAP") 357 | case proto.INS_NEW_MAP: 358 | pc++ 359 | fmt.Printf("MAP_NEW %d", getOpNum(text, pc)) 360 | skip += 4 361 | case proto.INS_ATTR_ASSIGN: 362 | fmt.Printf("ATTR_ASSIGN") 363 | case proto.INS_ATTR_ASSIGN_ADDEQ: 364 | fmt.Printf("ATTR_ASSIGN_ADDEQ") 365 | case proto.INS_ATTR_ASSIGN_SUBEQ: 366 | fmt.Printf("ATTR_ASSIGN_SUBEQ") 367 | case proto.INS_ATTR_ASSIGN_MULEQ: 368 | fmt.Printf("ATTR_ASSIGN_MULEQ") 369 | case proto.INS_ATTR_ASSIGN_DIVEQ: 370 | fmt.Printf("ATTR_ASSIGN_DIVEQ") 371 | case proto.INS_ATTR_ASSIGN_MODEQ: 372 | fmt.Printf("ATTR_ASSIGN_MODEQ") 373 | case proto.INS_ATTR_ASSIGN_ANDEQ: 374 | fmt.Printf("ATTR_ASSIGN_ANDEQ") 375 | case proto.INS_ATTR_ASSIGN_XOREQ: 376 | fmt.Printf("ATTR_ASSIGN_XOREQ") 377 | case proto.INS_ATTR_ASSIGN_OREQ: 378 | fmt.Printf("ATTR_ASSIGN_OREQ") 379 | case proto.INS_ATTR_ACCESS: 380 | fmt.Printf("ATTR_ACCESS") 381 | case proto.INS_JUMP_REL: 382 | pc++ 383 | fmt.Printf("JUMP_REL %d", int32(getOpNum(text, pc))) 384 | skip += 4 385 | case proto.INS_JUMP_ABS: 386 | pc++ 387 | fmt.Printf("JUMP_ABS %d", getOpNum(text, pc)) 388 | skip += 4 389 | case proto.INS_JUMP_IF: 390 | pc++ 391 | fmt.Printf("JUMP_IF %d", getOpNum(text, pc)) 392 | skip += 4 393 | case proto.INS_JUMP_LAND: 394 | pc++ 395 | fmt.Printf("JUMP_LAND %d", getOpNum(text, pc)) 396 | skip += 4 397 | case proto.INS_JUMP_LOR: 398 | pc++ 399 | fmt.Printf("JUMP_LOR %d", getOpNum(text, pc)) 400 | skip += 4 401 | case proto.INS_JUMP_CASE: 402 | pc++ 403 | fmt.Printf("JUMP_CASE %d", getOpNum(text, pc)) 404 | skip += 4 405 | case proto.INS_ROT_TWO: 406 | fmt.Printf("ROT_TWO") 407 | case proto.INS_EXPORT: 408 | fmt.Printf("EXPORT") 409 | case proto.INS_TRY: 410 | pc++ 411 | fmt.Printf("TRY %d", getOpNum(text, pc)) 412 | skip += 4 413 | case proto.INS_END_TRY: 414 | fmt.Printf("END_TRY") 415 | } 416 | fmt.Println() 417 | return uint32(skip) 418 | } 419 | 420 | func getOpNum(text []byte, pc uint32) uint32 { 421 | data := text[pc : pc+4] 422 | return binary.LittleEndian.Uint32(data) 423 | } 424 | -------------------------------------------------------------------------------- /vm/evstack.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | type evalStack struct { 4 | Buf []interface{} 5 | } 6 | 7 | func newEvalStack() *evalStack { 8 | return &evalStack{} 9 | } 10 | 11 | func (s *evalStack) Replace(v interface{}) { 12 | s.Buf[len(s.Buf)-1] = v 13 | } 14 | 15 | func (s *evalStack) Top() interface{} { 16 | return s.Buf[len(s.Buf)-1] 17 | } 18 | 19 | func (s *evalStack) top(n int) interface{} { 20 | return s.Buf[len(s.Buf)-n] 21 | } 22 | 23 | func (s *evalStack) popN(n int) { 24 | last := len(s.Buf) 25 | for i := 0; i < n; i++ { 26 | last-- 27 | s.Buf[last] = nil 28 | } 29 | s.Buf = s.Buf[:last] 30 | } 31 | 32 | func (s *evalStack) Pop() { 33 | last := len(s.Buf) - 1 34 | s.Buf[last] = nil 35 | s.Buf = s.Buf[:last] 36 | } 37 | 38 | func (s *evalStack) pop() (val interface{}) { 39 | last := len(s.Buf) - 1 40 | val = s.Buf[last] 41 | s.Buf[last] = nil 42 | s.Buf = s.Buf[:last] 43 | return 44 | } 45 | 46 | func (s *evalStack) Push(v interface{}) { 47 | s.Buf = append(s.Buf, v) 48 | } 49 | -------------------------------------------------------------------------------- /vm/proto_frame.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | import "gscript/proto" 4 | 5 | type protoFrame struct { 6 | topFrame *stackFrame 7 | frame *stackFrame 8 | stack *evalStack 9 | funcTable []proto.FuncProto 10 | anonymousTable []proto.AnonymousFuncProto 11 | filepath string 12 | prev *protoFrame 13 | } 14 | 15 | func newProtoFrame(_proto proto.Proto) *protoFrame { 16 | topFrame := newFuncFrame() 17 | topFrame.text = _proto.Text 18 | frame := &protoFrame{ 19 | topFrame: topFrame, 20 | frame: topFrame, 21 | stack: newEvalStack(), 22 | funcTable: _proto.Funcs, 23 | anonymousTable: _proto.AnonymousFuncs, 24 | filepath: _proto.FilePath, 25 | } 26 | return frame 27 | } 28 | -------------------------------------------------------------------------------- /vm/stack_frame.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | import ( 4 | "gscript/vm/types" 5 | ) 6 | 7 | type stackFrame struct { 8 | prev *stackFrame 9 | symbolTable *symbolTable 10 | wantRetCnt int 11 | pc uint32 12 | upValues []*types.GsValue 13 | text []byte 14 | tryInfos []tryInfo 15 | } 16 | 17 | func newFuncFrame() *stackFrame { 18 | return &stackFrame{ 19 | symbolTable: newSymbolTable(), 20 | } 21 | } 22 | 23 | type tryInfo struct { 24 | curVarCnt uint32 25 | catchAddr uint32 26 | } 27 | 28 | func (sf *stackFrame) pushTryInfo(addr uint32, varCnt uint32) { 29 | sf.tryInfos = append(sf.tryInfos, tryInfo{ 30 | curVarCnt: varCnt, 31 | catchAddr: addr, 32 | }) 33 | } 34 | 35 | func (sf *stackFrame) popTryInfo() (addr, varCnt uint32) { 36 | last := len(sf.tryInfos) - 1 37 | info := sf.tryInfos[last] 38 | sf.tryInfos = sf.tryInfos[:last] 39 | return info.catchAddr, info.curVarCnt 40 | } 41 | -------------------------------------------------------------------------------- /vm/symbol_tabel.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | import ( 4 | "gscript/vm/types" 5 | ) 6 | 7 | type symbolTable struct { 8 | values []*types.GsValue 9 | } 10 | 11 | func newSymbolTable() *symbolTable { 12 | return &symbolTable{} 13 | } 14 | 15 | func (st *symbolTable) getValue(idx uint32) interface{} { 16 | if idx >= uint32(len(st.values)) { 17 | exit("index(%d) out of variables table length(%d)", idx, len(st.values)) 18 | } 19 | return st.values[idx].Value 20 | } 21 | 22 | func (st *symbolTable) setValue(idx uint32, val interface{}) { 23 | if idx >= uint32(len(st.values)) { 24 | exit("index(%d) out of variables table length(%d)", idx, len(st.values)) 25 | } 26 | st.values[idx].Value = val 27 | } 28 | 29 | func (st *symbolTable) pushSymbol(val interface{}) { 30 | st.values = append(st.values, &types.GsValue{Value: val}) 31 | } 32 | 33 | func (st *symbolTable) top() (val interface{}) { 34 | return st.values[len(st.values)-1].Value 35 | } 36 | 37 | func (st *symbolTable) resizeTo(size int) { 38 | if size >= len(st.values) { 39 | return 40 | } 41 | for i := size; i < len(st.values); i++ { 42 | st.values[i] = nil 43 | } 44 | st.values = st.values[:size] 45 | } 46 | -------------------------------------------------------------------------------- /vm/types/array.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | type Array struct { 4 | Data []interface{} 5 | } 6 | 7 | func NewArray(data []interface{}) *Array { 8 | return &Array{Data: data} 9 | } 10 | -------------------------------------------------------------------------------- /vm/types/buffer.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | type Buffer struct { 4 | Data []byte 5 | } 6 | 7 | func NewBuffer(data []byte) *Buffer { 8 | return &Buffer{Data: data} 9 | } 10 | 11 | func NewBufferFromString(str string) *Buffer { 12 | return &Buffer{ 13 | Data: []byte(str), 14 | } 15 | } 16 | 17 | func NewBufferN(cap int) *Buffer { 18 | return &Buffer{Data: make([]byte, cap)} 19 | } 20 | -------------------------------------------------------------------------------- /vm/types/closure.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import "gscript/proto" 4 | 5 | type GsValue struct { 6 | Value interface{} 7 | } 8 | 9 | type Closure struct { 10 | Info *proto.BasicInfo 11 | UpValues []*GsValue 12 | } 13 | -------------------------------------------------------------------------------- /vm/types/file.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import "os" 4 | 5 | type File struct { 6 | File *os.File 7 | } 8 | 9 | func NewFile(file *os.File) *File { 10 | return &File{File: file} 11 | } 12 | -------------------------------------------------------------------------------- /vm/types/object.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | const MaxArrayCap = 8 4 | 5 | type KV struct { 6 | Key interface{} 7 | Val interface{} 8 | } 9 | 10 | // when kv count is <= 8, use Array to stores kvs, otherwise use Map 11 | type Object struct { 12 | Array []KV 13 | Map map[interface{}]interface{} 14 | } 15 | 16 | func NewObjectN(cap int) *Object { 17 | obj := &Object{} 18 | if cap > MaxArrayCap { 19 | obj.Map = make(map[interface{}]interface{}, cap) 20 | return obj 21 | } 22 | obj.Array = make([]KV, 0, MaxArrayCap) 23 | return obj 24 | } 25 | 26 | func NewObject() *Object { 27 | return &Object{Array: make([]KV, 0, MaxArrayCap)} 28 | } 29 | 30 | func (obj *Object) Set(k, v interface{}) { 31 | if obj.Map != nil { 32 | obj.Map[k] = v 33 | return 34 | } 35 | for i := range obj.Array { 36 | if obj.Array[i].Key == k { 37 | obj.Array[i].Val = v 38 | return 39 | } 40 | } 41 | if len(obj.Array) < MaxArrayCap { 42 | obj.Array = append(obj.Array, KV{k, v}) 43 | return 44 | } 45 | obj.Map = make(map[interface{}]interface{}, 16) 46 | for _, kv := range obj.Array { 47 | obj.Map[kv.Key] = kv.Val 48 | } 49 | obj.Map[k] = v 50 | } 51 | 52 | func (obj *Object) Get(k interface{}) interface{} { 53 | if obj.Map != nil { 54 | return obj.Map[k] 55 | } 56 | for i := range obj.Array { 57 | if obj.Array[i].Key == k { 58 | return obj.Array[i].Val 59 | } 60 | } 61 | return nil 62 | } 63 | 64 | func (obj *Object) KVCount() int { 65 | if obj.Map != nil { 66 | return len(obj.Map) 67 | } 68 | return len(obj.Array) 69 | } 70 | 71 | func (obj *Object) Clone() *Object { 72 | if obj.Map != nil { 73 | m := make(map[interface{}]interface{}, len(obj.Map)) 74 | for k, v := range obj.Map { 75 | m[k] = v 76 | } 77 | return &Object{Map: m} 78 | } 79 | arr := make([]KV, len(obj.Array), cap(obj.Array)) 80 | copy(arr, obj.Array) 81 | return &Object{Array: arr} 82 | } 83 | 84 | func (obj *Object) Delete(key interface{}) { 85 | if obj.Map != nil { 86 | delete(obj.Map, key) 87 | return 88 | } 89 | for i := range obj.Array { 90 | if obj.Array[i].Key == key { 91 | obj.Array[i].Val = nil 92 | } 93 | } 94 | } 95 | 96 | func (obj *Object) ForEach(cb func(k, v interface{})) { 97 | if obj.Map != nil { 98 | for k, v := range obj.Map { 99 | cb(k, v) 100 | } 101 | return 102 | } 103 | for _, kv := range obj.Array { 104 | cb(kv.Key, kv.Val) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /vm/vm.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "gscript/proto" 7 | "os" 8 | "unsafe" 9 | ) 10 | 11 | type VM struct { 12 | stopped bool 13 | protos []proto.Proto 14 | stdlibs []proto.Proto 15 | curProto *protoFrame 16 | builtinFuncFailed bool 17 | curCallingBuiltin string 18 | } 19 | 20 | func NewVM(protos []proto.Proto, stdlibs []proto.Proto) *VM { 21 | return &VM{ 22 | protos: protos, 23 | stdlibs: stdlibs, 24 | curProto: newProtoFrame(protos[0]), 25 | } 26 | } 27 | 28 | func (vm *VM) Run() { 29 | for { 30 | if vm.stopped { 31 | break 32 | } 33 | instruction := vm.curProto.frame.text[vm.curProto.frame.pc] 34 | vm.curProto.frame.pc++ 35 | Execute(vm, instruction) 36 | } 37 | } 38 | 39 | func (vm *VM) Stop() { 40 | vm.stopped = true 41 | } 42 | 43 | func (vm *VM) getOpNum() uint32 { 44 | arr := *(*[4]byte)(unsafe.Pointer(&vm.curProto.frame.text[vm.curProto.frame.pc])) 45 | v := binary.LittleEndian.Uint32(arr[:]) 46 | vm.curProto.frame.pc += 4 47 | return v 48 | } 49 | 50 | func (vm *VM) exit(format string, args ...interface{}) { 51 | fmt.Printf("[%s] ", vm.curProto.filepath) 52 | exit(format, args...) 53 | } 54 | 55 | func exit(format string, args ...interface{}) { 56 | fmt.Printf("runtime error: ") 57 | fmt.Printf(format, args...) 58 | os.Exit(0) 59 | } 60 | 61 | func (vm *VM) assert(cond bool) { 62 | if cond { 63 | return 64 | } 65 | fmt.Printf("[%s] runtime error: ", vm.curProto.filepath) 66 | fmt.Printf("call builtin function '%s' failed, ", vm.curCallingBuiltin) 67 | fmt.Println("please check count and type of arguments") 68 | os.Exit(0) 69 | } 70 | --------------------------------------------------------------------------------