├── .github └── workflows │ └── build.yml ├── .gitignore ├── .gitmodules ├── LICENSE ├── Makefile ├── README.md ├── cmd └── tau │ └── main.go ├── editors └── sublime-text │ ├── Comments.tmPreferences │ └── tau.sublime-syntax ├── examples ├── fib.tau ├── goselect.tau ├── hello.tau ├── import │ ├── import_test.tau │ └── main.tau ├── input.tau ├── plugin │ ├── Makefile │ └── main.go └── plugins.tau ├── go.mod ├── go.sum ├── internal ├── ast │ ├── and.go │ ├── assign.go │ ├── bang.go │ ├── bitwiseand.go │ ├── bitwiseandassign.go │ ├── bitwisenot.go │ ├── bitwiseor.go │ ├── bitwiseorassign.go │ ├── bitwiseshiftleft.go │ ├── bitwiseshiftleftassign.go │ ├── bitwiseshiftright.go │ ├── bitwiseshiftrightassign.go │ ├── bitwisexor.go │ ├── bitwisexorassign.go │ ├── block.go │ ├── boolean.go │ ├── break.go │ ├── call.go │ ├── concurrentcall.go │ ├── continue.go │ ├── divide.go │ ├── divideassign.go │ ├── dot.go │ ├── equals.go │ ├── float.go │ ├── for.go │ ├── function.go │ ├── greater.go │ ├── greatereq.go │ ├── identifier.go │ ├── ifelse.go │ ├── import.go │ ├── index.go │ ├── integer.go │ ├── less.go │ ├── lesseq.go │ ├── list.go │ ├── map.go │ ├── minus.go │ ├── minusassign.go │ ├── minusminus.go │ ├── mod.go │ ├── modassign.go │ ├── node.go │ ├── notequals.go │ ├── null.go │ ├── or.go │ ├── plus.go │ ├── plusassign.go │ ├── plusplus.go │ ├── prefixmin.go │ ├── rawstring.go │ ├── return.go │ ├── string.go │ ├── times.go │ └── timesassign.go ├── code │ ├── code.go │ ├── code_test.go │ └── opcode_string.go ├── compiler │ ├── bytecode.h │ ├── codec.c │ ├── compiler.go │ ├── scopes_test.go │ ├── symboltable.go │ └── symboltable_test.go ├── item │ ├── item.go │ └── type.go ├── lexer │ ├── lexer.go │ └── lexer_test.go ├── obj │ ├── boolean.c │ ├── builtins.c │ ├── bytes.c │ ├── closure.c │ ├── error.c │ ├── float.c │ ├── function.c │ ├── integer.c │ ├── list.c │ ├── map.c │ ├── null.c │ ├── object.c │ ├── object.go │ ├── object.h │ ├── pipe.c │ ├── plugin.h │ ├── string.c │ └── utils.c ├── parser │ └── parser.go ├── tau_test.go ├── tauerr │ ├── bookmark.go │ ├── bookmark.h │ └── tauerr.go └── vm │ ├── heap.c │ ├── jump_table.h │ ├── opcode.h │ ├── pool.c │ ├── thrd.h │ ├── vm.c │ ├── vm.go │ └── vm.h ├── profile.go ├── redirect.go ├── redirect_win.go ├── repl.go ├── stdlib ├── errno.tau ├── os.tau └── strings.tau └── tau.go /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | --- 2 | on: 3 | push: 4 | tags: 5 | - "v*" 6 | 7 | permissions: 8 | contents: write 9 | 10 | name: Build and release 11 | 12 | jobs: 13 | create_release: 14 | runs-on: ubuntu-latest 15 | outputs: 16 | upload_url: ${{ steps.create_release.outputs.upload_url }} 17 | steps: 18 | - name: Checkout repository 19 | uses: actions/checkout@v4 20 | 21 | - name: Create GitHub Release 22 | id: create_release 23 | uses: actions/create-release@v1 24 | env: 25 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 26 | with: 27 | tag_name: ${{ github.ref }} 28 | release_name: Tau ${{ github.ref }} 29 | draft: false 30 | prerelease: false 31 | 32 | # Build for Linux (both x86_64 and aarch64) 33 | build-linux: 34 | runs-on: ubuntu-latest 35 | needs: create_release 36 | strategy: 37 | fail-fast: false 38 | matrix: 39 | go-version: [1.23.x] 40 | arch: [amd64, arm64] 41 | 42 | steps: 43 | - name: Setup Go 44 | uses: actions/setup-go@v3 45 | with: 46 | go-version: ${{ matrix.go-version }} 47 | 48 | - name: Checkout repository 49 | uses: actions/checkout@v4 50 | 51 | - name: Initialize and update submodules 52 | run: git submodule init && git submodule update 53 | 54 | - name: Build for Linux 55 | run: make && mv tau tau-linux-${{ matrix.arch }} 56 | 57 | - name: Upload Linux artifact to release 58 | uses: actions/upload-release-asset@v1 59 | env: 60 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 61 | with: 62 | upload_url: ${{ needs.create_release.outputs.upload_url }} 63 | asset_path: ./tau-linux-${{ matrix.arch }} 64 | asset_name: tau-linux-${{ matrix.arch }} 65 | asset_content_type: application/octet-stream 66 | 67 | # Build for Windows (only x86_64) 68 | build-windows: 69 | runs-on: ubuntu-latest 70 | needs: create_release 71 | strategy: 72 | fail-fast: false 73 | matrix: 74 | go-version: [1.23.x] 75 | arch: [amd64, arm64] 76 | 77 | steps: 78 | - name: Setup Go 79 | uses: actions/setup-go@v3 80 | with: 81 | go-version: 1.23.x 82 | 83 | - name: Checkout repository 84 | uses: actions/checkout@v4 85 | 86 | - name: Install MinGW 87 | run: sudo apt install mingw-w64 -y 88 | 89 | - name: Initialize and update submodules 90 | run: git submodule init && git submodule update 91 | 92 | - name: Build for Windows x86_64 93 | run: make windows && mv tau.exe tau-windows-x86_64.exe 94 | 95 | - name: Upload Windows artifact to release 96 | uses: actions/upload-release-asset@v1 97 | env: 98 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 99 | with: 100 | upload_url: ${{ needs.create_release.outputs.upload_url }} 101 | asset_path: ./tau-windows-x86_64.exe 102 | asset_name: tau-windows-x86_64.exe 103 | asset_content_type: application/octet-stream 104 | 105 | # Build for MacOS 106 | build-macos: 107 | runs-on: macos-latest 108 | needs: create_release 109 | strategy: 110 | fail-fast: false 111 | matrix: 112 | go-version: [1.23.x] 113 | arch: [amd64, arm64] 114 | 115 | steps: 116 | - name: Build MacOS 117 | uses: actions/setup-go@v3 118 | with: 119 | go-version: ${{ matrix.go-version }} 120 | - uses: actions/checkout@v4 121 | - run: git submodule init && git submodule update 122 | - run: brew install gcc@14 make automake libtool texinfo autoconf 123 | - run: make CC=gcc-14 && mv tau tau-macos-${{ matrix.arch }} 124 | 125 | - name: Upload MacOS artifact to release 126 | uses: actions/upload-release-asset@v1 127 | env: 128 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 129 | with: 130 | upload_url: ${{ needs.create_release.outputs.upload_url }} 131 | asset_path: ./tau-macos-${{ matrix.arch }} 132 | asset_name: tau-macos-${{ matrix.arch }} 133 | asset_content_type: application/octet-stream 134 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.bak 3 | *.exe 4 | *.tau 5 | *.tauc 6 | *.prof 7 | *.py 8 | *.so 9 | *.prof 10 | 11 | /.vscode 12 | **/.DS_store 13 | 14 | /tau 15 | /cmd/tau/tau 16 | !/examples/*.tau 17 | internal/obj/libffi 18 | internal/vm/bdwgc 19 | profile 20 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "libffi"] 2 | path = libffi 3 | url = https://github.com/libffi/libffi 4 | tag = v3.4.6 5 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | DIR := $(shell pwd) 2 | GCC := $(shell which gcc) 3 | DEFAULT_CC = $(CC) 4 | 5 | CFLAGS = -g -Ofast -I$(DIR)/internal/obj/libffi/include 6 | LDFLAGS = -L$(DIR)/internal/obj/libffi/lib $(DIR)/internal/obj/libffi/lib/libffi.a -lm 7 | 8 | UNAME_S := $(shell uname -s) 9 | ifeq ($(UNAME_S),Linux) 10 | ACLOCAL_PATH := /usr/share/aclocal 11 | INSTALL_PATH := /usr/bin 12 | endif 13 | ifeq ($(UNAME_S),Darwin) 14 | ACLOCAL_PATH := /usr/local/share/aclocal 15 | INSTALL_PATH := /usr/local/bin 16 | GCC := $(shell which gcc-14) 17 | endif 18 | 19 | # Check if CC is defined 20 | ifneq ($(origin CC), undefined) 21 | # Check if CC is not clang 22 | ifneq ($(CC), clang) 23 | # Check if the compiler is actually GCC by looking for "GCC" in the version output 24 | GCC_CHECK := $(shell $(CC) --version 2>/dev/null | head -n 1 | grep -i "gcc") 25 | ifneq ($(GCC_CHECK),) 26 | CFLAGS += -fopenmp 27 | LDFLAGS += -fopenmp 28 | endif 29 | endif 30 | endif 31 | 32 | # Default compiler fallback to GCC if GCC environment variable is set 33 | ifneq ($(GCC),) 34 | CC = $(GCC) 35 | endif 36 | 37 | .PHONY: all tau libffi install profile run 38 | 39 | all: libffi tau 40 | 41 | libffi: 42 | if [ ! -d libffi ] || [ $$(ls -1q libffi | wc -l) -eq 0 ]; then \ 43 | git submodule init; \ 44 | git submodule update --recursive; \ 45 | fi 46 | 47 | CC=$(CC) cd libffi && \ 48 | ACLOCAL_PATH=$(ACLOCAL_PATH) autoreconf -i && \ 49 | ./configure --prefix=$(DIR)/internal/obj/libffi --disable-shared --enable-static --disable-multi-os-directory && \ 50 | make install CC=$(CC) 51 | 52 | libffi-windows: 53 | if [ ! -d libffi ] || [ $$(ls -1q libffi | wc -l) -eq 0 ]; then \ 54 | git submodule init; \ 55 | git submodule update --recursive; \ 56 | fi 57 | 58 | CC=$(CC) cd libffi && \ 59 | ACLOCAL_PATH=$(ACLOCAL_PATH) autoreconf -i && \ 60 | ./configure --host=x86_64-w64-mingw32 --prefix=$(DIR)/internal/obj/libffi --disable-shared --enable-static --disable-multi-os-directory && \ 61 | make install CC=x86_64-w64-mingw32-gcc AR=x86_64-w64-mingw32-ar RANLIB=x86_64-w64-mingw32-ranlib 62 | 63 | tau: 64 | cd cmd/tau && \ 65 | CC=$(CC) \ 66 | CGO_CFLAGS="$(CFLAGS)" \ 67 | CGO_LDFLAGS="$(LDFLAGS)" \ 68 | go build -o $(DIR)/tau 69 | 70 | tau-windows: 71 | cd cmd/tau && \ 72 | CC=x86_64-w64-mingw32-gcc \ 73 | RANLIB=x86_64-w64-mingw32-ranlib \ 74 | CGO_ENABLED=1 \ 75 | CGO_CFLAGS="$(CFLAGS)" \ 76 | CGO_LDFLAGS="$(LDFLAGS)" \ 77 | GOOS=windows \ 78 | GOARCH=amd64 \ 79 | go build -o $(DIR)/tau.exe 80 | 81 | windows: libffi-windows tau-windows 82 | 83 | debug: 84 | cd cmd/tau && \ 85 | CC=$(CC) CGO_CFLAGS="$(CFLAGS) -DDEBUG" CGO_LDFLAGS="$(LDFLAGS)" go build -o $(DIR)/tau 86 | 87 | gc-debug: 88 | cd cmd/tau && \ 89 | CC=$(CC) CGO_CFLAGS="$(CFLAGS) -DGC_DEBUG" CGO_LDFLAGS="$(LDFLAGS)" go build -o $(DIR)/tau 90 | 91 | install: 92 | mkdir -p ~/.local/bin 93 | mkdir -p ~/.local/lib/tau 94 | cp tau ~/.local/bin/tau 95 | cp -r stdlib/* ~/.local/lib/tau 96 | 97 | profile: 98 | CC=$(CC) CGO_CFLAGS="$(CFLAGS)" CGO_LDFLAGS="$(LDFLAGS)" go build profile.go 99 | 100 | test: 101 | CC=$(CC) CGO_CFLAGS="$(CFLAGS) -DDEBUG -DGC_DEBUG" CGO_LDFLAGS="$(LDFLAGS)" go test ./... 102 | 103 | run: all 104 | ./tau 105 | -------------------------------------------------------------------------------- /cmd/tau/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "os" 6 | "runtime" 7 | 8 | "github.com/NicoNex/tau" 9 | ) 10 | 11 | func main() { 12 | var ( 13 | compile bool 14 | version bool 15 | simple bool 16 | ) 17 | 18 | flag.BoolVar(&compile, "c", false, "Compile a tau file into a '.tauc' bytecode file.") 19 | flag.BoolVar(&simple, "s", false, "Use simple REPL instead of opening a terminal.") 20 | flag.BoolVar(&version, "v", false, "Print Tau version information.") 21 | flag.Parse() 22 | 23 | switch { 24 | case compile: 25 | tau.CompileFiles(flag.Args()) 26 | case version: 27 | tau.PrintVersionInfo(os.Stdout) 28 | case flag.NArg() > 0: 29 | tau.ExecFileVM(flag.Arg(0)) 30 | case simple || runtime.GOOS == "windows": 31 | tau.SimpleREPL() 32 | default: 33 | tau.REPL() 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /editors/sublime-text/Comments.tmPreferences: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | name 5 | Comments 6 | scope 7 | source.tau 8 | settings 9 | 10 | shellVariables 11 | 12 | 13 | name 14 | TM_COMMENT_START 15 | value 16 | # 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /examples/fib.tau: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env tau 2 | 3 | fib = fn(n) { 4 | if n < 2 { 5 | return n 6 | } 7 | fib(n-1) + fib(n-2) 8 | } 9 | 10 | println(fib(40)) 11 | -------------------------------------------------------------------------------- /examples/goselect.tau: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env tau 2 | 3 | # message returns an object that contains additional information to 4 | # the provided data and is used by processMessages. 5 | message = fn(id, data) { 6 | m = new() 7 | m.ID = id 8 | m.Data = data 9 | 10 | return m 11 | } 12 | 13 | # plumber listens from the data pipe (dataPipe) and sends the received data object 14 | # to the message pipe (msgPipe) including the provided ID. 15 | plumber = fn(id, dataPipe, msgPipe) { 16 | for d = recv(dataPipe) { 17 | send(msgPipe, message(id, d)) 18 | } 19 | } 20 | 21 | someFunction = fn(p) { 22 | # Do something with the pipe in input. 23 | } 24 | 25 | someOtherFunction = fn(p) { 26 | # Do something with the pipe in input. 27 | } 28 | 29 | main = fn() { 30 | p = pipe() 31 | stop = pipe() 32 | messages = pipe() 33 | 34 | tau plumber(0, stop, messages) 35 | tau plumber(1, p, messages) 36 | 37 | tau someFunction(p) 38 | tau someOtherFunction(stop) 39 | 40 | # listen for messages in the message pipe (msgPipe) and runs 41 | # a different operation for each received ID. 42 | for msg = recv(msgPipe) { 43 | if msg.ID == 0 { 44 | close(p) 45 | close(stop) 46 | close(messages) 47 | break 48 | } else if msg.ID == 1 { 49 | # do something with msg.Data 50 | println(msg.Data) 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /examples/hello.tau: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env tau 2 | 3 | println("Hello World") 4 | -------------------------------------------------------------------------------- /examples/import/import_test.tau: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env tau 2 | 3 | data = 123 4 | 5 | printData = fn() { 6 | println(data) 7 | } 8 | 9 | printText = fn() { 10 | println("example text") 11 | } 12 | 13 | TestPrint = fn() { 14 | printData() 15 | printText() 16 | } 17 | 18 | dog = fn(name, age) { 19 | d = new() 20 | d.Name = name 21 | d.Age = age 22 | d.id = 456 23 | 24 | d.ID = fn() { 25 | d.id 26 | } 27 | 28 | return d 29 | } 30 | 31 | Snuffles = dog("Mr Snuffles", 5) 32 | -------------------------------------------------------------------------------- /examples/import/main.tau: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env tau 2 | 3 | it = import("import_test") 4 | 5 | it.TestPrint() 6 | 7 | println(it.Snuffles.Name) 8 | println(it.Snuffles.Age) 9 | println(it.Snuffles.ID()) 10 | -------------------------------------------------------------------------------- /examples/input.tau: -------------------------------------------------------------------------------- 1 | name = input("What is your name? ") 2 | printf("Hello %s\n", name) 3 | -------------------------------------------------------------------------------- /examples/plugin/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: build 2 | 3 | GOCMD=go 4 | 5 | all: build 6 | 7 | build: plugin.so 8 | 9 | plugin.so: 10 | $(GOCMD) build -buildmode=plugin -o plugin.so 11 | -------------------------------------------------------------------------------- /examples/plugin/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func Hello() { 6 | fmt.Println("Hello World") 7 | } 8 | 9 | func Sum(a, b int) int { 10 | return a + b 11 | } 12 | -------------------------------------------------------------------------------- /examples/plugins.tau: -------------------------------------------------------------------------------- 1 | p = plugin("plugin/plugin.so") 2 | p.Hello() 3 | 4 | p.Sum(3, 4) 5 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/NicoNex/tau 2 | 3 | go 1.19 4 | 5 | require ( 6 | golang.org/x/sys v0.15.0 7 | golang.org/x/term v0.15.0 8 | ) 9 | 10 | require github.com/ianlancetaylor/cgosymbolizer v0.0.0-20230801000641-8736a9d41aaa // indirect 11 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/ianlancetaylor/cgosymbolizer v0.0.0-20230801000641-8736a9d41aaa h1:FEZID0R3+pkWLvjmZJ2iL+SZTcb2+/PgVvoyQss/q/I= 2 | github.com/ianlancetaylor/cgosymbolizer v0.0.0-20230801000641-8736a9d41aaa/go.mod h1:DvXTE/K/RtHehxU8/GtDs4vFtfw64jJ3PaCnFri8CRg= 3 | golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= 4 | golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 5 | golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= 6 | golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= 7 | -------------------------------------------------------------------------------- /internal/ast/and.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/NicoNex/tau/internal/code" 7 | "github.com/NicoNex/tau/internal/compiler" 8 | "github.com/NicoNex/tau/internal/obj" 9 | ) 10 | 11 | type And struct { 12 | l Node 13 | r Node 14 | pos int 15 | } 16 | 17 | func NewAnd(l, r Node, pos int) Node { 18 | return And{ 19 | l: l, 20 | r: r, 21 | pos: pos, 22 | } 23 | } 24 | 25 | func (a And) Eval() (obj.Object, error) { 26 | left, err := a.l.Eval() 27 | if err != nil { 28 | return obj.NullObj, err 29 | } 30 | 31 | right, err := a.r.Eval() 32 | if err != nil { 33 | return obj.NullObj, err 34 | } 35 | 36 | return obj.ParseBool(left.IsTruthy() && right.IsTruthy()), nil 37 | } 38 | 39 | func (a And) String() string { 40 | return fmt.Sprintf("(%v && %v)", a.l, a.r) 41 | } 42 | 43 | func (a And) Compile(c *compiler.Compiler) (position int, err error) { 44 | if a.IsConstExpression() { 45 | o, err := a.Eval() 46 | if err != nil { 47 | return 0, c.NewError(a.pos, err.Error()) 48 | } 49 | position = c.Emit(code.OpConstant, c.AddConstant(o)) 50 | c.Bookmark(a.pos) 51 | return position, err 52 | } 53 | 54 | if position, err = a.l.Compile(c); err != nil { 55 | return 56 | } 57 | 58 | jntPos := c.Emit(code.OpJumpNotTruthy, compiler.GenericPlaceholder) 59 | // Emit OpTrue because the value will be popped from the stack. 60 | position = c.Emit(code.OpTrue) 61 | position, err = a.r.Compile(c) 62 | if err != nil { 63 | return 64 | } 65 | position = c.Emit(code.OpAnd) 66 | jmpPos := c.Emit(code.OpJump, compiler.GenericPlaceholder) 67 | // Emit OpFalse because the expression needs to return false if jumped here. 68 | position = c.Emit(code.OpFalse) 69 | c.ReplaceOperand(jntPos, position) 70 | c.ReplaceOperand(jmpPos, c.Pos()) 71 | c.Bookmark(a.pos) 72 | 73 | return 74 | } 75 | 76 | func (a And) IsConstExpression() bool { 77 | return a.l.IsConstExpression() && a.r.IsConstExpression() 78 | } 79 | -------------------------------------------------------------------------------- /internal/ast/assign.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "github.com/NicoNex/tau/internal/code" 8 | "github.com/NicoNex/tau/internal/compiler" 9 | "github.com/NicoNex/tau/internal/obj" 10 | ) 11 | 12 | type Assign struct { 13 | l Node 14 | r Node 15 | pos int 16 | } 17 | 18 | type Definable interface { 19 | CompileDefine(c *compiler.Compiler) (position int, err error) 20 | } 21 | 22 | func NewAssign(l, r Node, pos int) Node { 23 | return Assign{ 24 | l: l, 25 | r: r, 26 | pos: pos, 27 | } 28 | } 29 | 30 | func (a Assign) Eval() (obj.Object, error) { 31 | return obj.NullObj, errors.New("ast.Assign: not a constant expression") 32 | } 33 | 34 | func (a Assign) String() string { 35 | return fmt.Sprintf("(%v = %v)", a.l, a.r) 36 | } 37 | 38 | func (a Assign) Compile(c *compiler.Compiler) (position int, err error) { 39 | defer c.Bookmark(position) 40 | 41 | switch left := a.l.(type) { 42 | case Identifier: 43 | symbol := c.Define(left.String()) 44 | if position, err = a.r.Compile(c); err != nil { 45 | return 46 | } 47 | 48 | if symbol.Scope == compiler.GlobalScope { 49 | position = c.Emit(code.OpSetGlobal, symbol.Index) 50 | c.Bookmark(a.pos) 51 | return 52 | } else { 53 | position = c.Emit(code.OpSetLocal, symbol.Index) 54 | c.Bookmark(a.pos) 55 | return 56 | } 57 | 58 | case Definable: 59 | if position, err = left.CompileDefine(c); err != nil { 60 | return 61 | } 62 | if position, err = a.r.Compile(c); err != nil { 63 | return 64 | } 65 | position = c.Emit(code.OpDefine) 66 | c.Bookmark(a.pos) 67 | return 68 | 69 | default: 70 | return 0, fmt.Errorf("cannot assign to literal") 71 | } 72 | } 73 | 74 | func (a Assign) IsConstExpression() bool { 75 | return false 76 | } 77 | -------------------------------------------------------------------------------- /internal/ast/bang.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/NicoNex/tau/internal/code" 7 | "github.com/NicoNex/tau/internal/compiler" 8 | "github.com/NicoNex/tau/internal/obj" 9 | ) 10 | 11 | type Bang struct { 12 | n Node 13 | pos int 14 | } 15 | 16 | func NewBang(n Node, pos int) Node { 17 | return Bang{ 18 | n: n, 19 | pos: pos, 20 | } 21 | } 22 | 23 | func (b Bang) Eval() (obj.Object, error) { 24 | value, err := b.n.Eval() 25 | if err != nil { 26 | return obj.NullObj, err 27 | } 28 | return obj.ParseBool(!value.IsTruthy()), nil 29 | } 30 | 31 | func (b Bang) String() string { 32 | return fmt.Sprintf("!%v", b.n) 33 | } 34 | 35 | func (b Bang) Compile(c *compiler.Compiler) (position int, err error) { 36 | if b.IsConstExpression() { 37 | o, err := b.Eval() 38 | if err != nil { 39 | return 0, c.NewError(b.pos, err.Error()) 40 | } 41 | position = c.Emit(code.OpConstant, c.AddConstant(o)) 42 | c.Bookmark(b.pos) 43 | return position, err 44 | } 45 | 46 | if position, err = b.n.Compile(c); err != nil { 47 | return 48 | } 49 | position = c.Emit(code.OpBang) 50 | c.Bookmark(b.pos) 51 | return 52 | } 53 | 54 | func (b Bang) IsConstExpression() bool { 55 | return b.n.IsConstExpression() 56 | } 57 | -------------------------------------------------------------------------------- /internal/ast/bitwiseand.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/NicoNex/tau/internal/code" 7 | "github.com/NicoNex/tau/internal/compiler" 8 | "github.com/NicoNex/tau/internal/obj" 9 | ) 10 | 11 | type BitwiseAnd struct { 12 | l Node 13 | r Node 14 | pos int 15 | } 16 | 17 | func NewBitwiseAnd(l, r Node, pos int) Node { 18 | return BitwiseAnd{ 19 | l: l, 20 | r: r, 21 | pos: pos, 22 | } 23 | } 24 | 25 | func (b BitwiseAnd) Eval() (obj.Object, error) { 26 | left, err := b.l.Eval() 27 | if err != nil { 28 | return obj.NullObj, err 29 | } 30 | 31 | right, err := b.r.Eval() 32 | if err != nil { 33 | return obj.NullObj, err 34 | } 35 | 36 | if !obj.AssertTypes(left, obj.IntType) { 37 | return obj.NullObj, fmt.Errorf("unsupported operator '&' for type %v", left.Type()) 38 | } 39 | if !obj.AssertTypes(right, obj.IntType) { 40 | return obj.NullObj, fmt.Errorf("unsupported operator '&' for type %v", right.Type()) 41 | } 42 | return obj.NewInteger(left.Int() & right.Int()), nil 43 | } 44 | 45 | func (b BitwiseAnd) String() string { 46 | return fmt.Sprintf("(%v & %v)", b.l, b.r) 47 | } 48 | 49 | func (b BitwiseAnd) Compile(c *compiler.Compiler) (position int, err error) { 50 | if b.IsConstExpression() { 51 | o, err := b.Eval() 52 | if err != nil { 53 | return 0, c.NewError(b.pos, err.Error()) 54 | } 55 | position = c.Emit(code.OpConstant, c.AddConstant(o)) 56 | c.Bookmark(b.pos) 57 | return position, err 58 | } 59 | 60 | if position, err = b.l.Compile(c); err != nil { 61 | return 62 | } 63 | if position, err = b.r.Compile(c); err != nil { 64 | return 65 | } 66 | position = c.Emit(code.OpBwAnd) 67 | c.Bookmark(b.pos) 68 | return 69 | } 70 | 71 | func (b BitwiseAnd) IsConstExpression() bool { 72 | return b.l.IsConstExpression() && b.r.IsConstExpression() 73 | } 74 | -------------------------------------------------------------------------------- /internal/ast/bitwiseandassign.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "github.com/NicoNex/tau/internal/compiler" 8 | "github.com/NicoNex/tau/internal/obj" 9 | ) 10 | 11 | type BitwiseAndAssign struct { 12 | l Node 13 | r Node 14 | pos int 15 | } 16 | 17 | func NewBitwiseAndAssign(l, r Node, pos int) Node { 18 | return BitwiseAndAssign{ 19 | l: l, 20 | r: r, 21 | pos: pos, 22 | } 23 | } 24 | 25 | func (b BitwiseAndAssign) Eval() (obj.Object, error) { 26 | return obj.NullObj, errors.New("ast.BitwiseAndAssign: not a constant expression") 27 | } 28 | 29 | func (b BitwiseAndAssign) String() string { 30 | return fmt.Sprintf("(%v &= %v)", b.l, b.r) 31 | } 32 | 33 | func (b BitwiseAndAssign) Compile(c *compiler.Compiler) (position int, err error) { 34 | n := Assign{l: b.l, r: BitwiseAnd{l: b.l, r: b.r, pos: b.pos}, pos: b.pos} 35 | position, err = n.Compile(c) 36 | c.Bookmark(b.pos) 37 | return 38 | } 39 | 40 | func (b BitwiseAndAssign) IsConstExpression() bool { 41 | return false 42 | } 43 | -------------------------------------------------------------------------------- /internal/ast/bitwisenot.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/NicoNex/tau/internal/code" 7 | "github.com/NicoNex/tau/internal/compiler" 8 | "github.com/NicoNex/tau/internal/obj" 9 | ) 10 | 11 | type BitwiseNot struct { 12 | n Node 13 | pos int 14 | } 15 | 16 | func NewBitwiseNot(n Node, pos int) Node { 17 | return BitwiseNot{ 18 | n: n, 19 | pos: pos, 20 | } 21 | } 22 | 23 | func (b BitwiseNot) Eval() (obj.Object, error) { 24 | value, err := b.n.Eval() 25 | if err != nil { 26 | return obj.NullObj, err 27 | } 28 | 29 | if !obj.AssertTypes(value, obj.IntType) { 30 | return obj.NullObj, fmt.Errorf("unsupported operator '~' for type %v", value.Type()) 31 | } 32 | 33 | return obj.NewInteger(^value.Int()), nil 34 | } 35 | 36 | func (b BitwiseNot) String() string { 37 | return fmt.Sprintf("~%v", b.n) 38 | } 39 | 40 | func (b BitwiseNot) Compile(c *compiler.Compiler) (position int, err error) { 41 | if b.IsConstExpression() { 42 | o, err := b.Eval() 43 | if err != nil { 44 | return 0, c.NewError(b.pos, err.Error()) 45 | } 46 | position = c.Emit(code.OpConstant, c.AddConstant(o)) 47 | c.Bookmark(b.pos) 48 | return position, err 49 | } 50 | 51 | if position, err = b.n.Compile(c); err != nil { 52 | return 53 | } 54 | position = c.Emit(code.OpBwNot) 55 | c.Bookmark(b.pos) 56 | return 57 | } 58 | 59 | func (b BitwiseNot) IsConstExpression() bool { 60 | return b.n.IsConstExpression() 61 | } 62 | -------------------------------------------------------------------------------- /internal/ast/bitwiseor.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/NicoNex/tau/internal/code" 7 | "github.com/NicoNex/tau/internal/compiler" 8 | "github.com/NicoNex/tau/internal/obj" 9 | ) 10 | 11 | type BitwiseOr struct { 12 | l Node 13 | r Node 14 | pos int 15 | } 16 | 17 | func NewBitwiseOr(l, r Node, pos int) Node { 18 | return BitwiseOr{ 19 | l: l, 20 | r: r, 21 | pos: pos, 22 | } 23 | } 24 | 25 | func (b BitwiseOr) Eval() (obj.Object, error) { 26 | left, err := b.l.Eval() 27 | if err != nil { 28 | return obj.NullObj, err 29 | } 30 | 31 | right, err := b.r.Eval() 32 | if err != nil { 33 | return obj.NullObj, err 34 | } 35 | 36 | if !obj.AssertTypes(left, obj.IntType) { 37 | return obj.NullObj, fmt.Errorf("unsupported operator '|' for type %v", left.Type()) 38 | } 39 | if !obj.AssertTypes(right, obj.IntType) { 40 | return obj.NullObj, fmt.Errorf("unsupported operator '|' for type %v", right.Type()) 41 | } 42 | return obj.NewInteger(left.Int() | right.Int()), nil 43 | } 44 | 45 | func (b BitwiseOr) String() string { 46 | return fmt.Sprintf("(%v | %v)", b.l, b.r) 47 | } 48 | 49 | func (b BitwiseOr) Compile(c *compiler.Compiler) (position int, err error) { 50 | if b.IsConstExpression() { 51 | o, err := b.Eval() 52 | if err != nil { 53 | return 0, c.NewError(b.pos, err.Error()) 54 | } 55 | position = c.Emit(code.OpConstant, c.AddConstant(o)) 56 | c.Bookmark(b.pos) 57 | return position, err 58 | } 59 | 60 | if position, err = b.l.Compile(c); err != nil { 61 | return 62 | } 63 | if position, err = b.r.Compile(c); err != nil { 64 | return 65 | } 66 | position = c.Emit(code.OpBwOr) 67 | c.Bookmark(b.pos) 68 | return 69 | } 70 | 71 | func (b BitwiseOr) IsConstExpression() bool { 72 | return b.l.IsConstExpression() && b.r.IsConstExpression() 73 | } 74 | -------------------------------------------------------------------------------- /internal/ast/bitwiseorassign.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "github.com/NicoNex/tau/internal/compiler" 8 | "github.com/NicoNex/tau/internal/obj" 9 | ) 10 | 11 | type BitwiseOrAssign struct { 12 | l Node 13 | r Node 14 | pos int 15 | } 16 | 17 | func NewBitwiseOrAssign(l, r Node, pos int) Node { 18 | return BitwiseOrAssign{ 19 | l: l, 20 | r: r, 21 | pos: pos, 22 | } 23 | } 24 | 25 | func (b BitwiseOrAssign) Eval() (obj.Object, error) { 26 | return obj.NullObj, errors.New("ast.BitwiseOrAssign: not a constant expression") 27 | } 28 | 29 | func (b BitwiseOrAssign) String() string { 30 | return fmt.Sprintf("(%v |= %v)", b.l, b.r) 31 | } 32 | 33 | func (b BitwiseOrAssign) Compile(c *compiler.Compiler) (position int, err error) { 34 | n := Assign{l: b.l, r: BitwiseOr{l: b.l, r: b.r, pos: b.pos}, pos: b.pos} 35 | position, err = n.Compile(c) 36 | c.Bookmark(n.pos) 37 | return 38 | } 39 | 40 | func (b BitwiseOrAssign) IsConstExpression() bool { 41 | return false 42 | } 43 | -------------------------------------------------------------------------------- /internal/ast/bitwiseshiftleft.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/NicoNex/tau/internal/code" 7 | "github.com/NicoNex/tau/internal/compiler" 8 | "github.com/NicoNex/tau/internal/obj" 9 | ) 10 | 11 | type BitwiseLeftShift struct { 12 | l Node 13 | r Node 14 | pos int 15 | } 16 | 17 | func NewBitwiseLeftShift(l, r Node, pos int) Node { 18 | return BitwiseLeftShift{ 19 | l: l, 20 | r: r, 21 | pos: pos, 22 | } 23 | } 24 | 25 | func (b BitwiseLeftShift) Eval() (obj.Object, error) { 26 | left, err := b.l.Eval() 27 | if err != nil { 28 | return obj.NullObj, err 29 | } 30 | 31 | right, err := b.r.Eval() 32 | if err != nil { 33 | return obj.NullObj, err 34 | } 35 | 36 | if !obj.AssertTypes(left, obj.IntType) { 37 | return obj.NullObj, fmt.Errorf("unsupported operator '<<' for type %v", left.Type()) 38 | } 39 | if !obj.AssertTypes(right, obj.IntType) { 40 | return obj.NullObj, fmt.Errorf("unsupported operator '<<' for type %v", right.Type()) 41 | } 42 | return obj.NewInteger(left.Int() << right.Int()), nil 43 | } 44 | 45 | func (b BitwiseLeftShift) String() string { 46 | return fmt.Sprintf("(%v << %v)", b.l, b.r) 47 | } 48 | 49 | func (b BitwiseLeftShift) Compile(c *compiler.Compiler) (position int, err error) { 50 | if b.IsConstExpression() { 51 | o, err := b.Eval() 52 | if err != nil { 53 | return 0, c.NewError(b.pos, err.Error()) 54 | } 55 | position = c.Emit(code.OpConstant, c.AddConstant(o)) 56 | c.Bookmark(b.pos) 57 | return position, err 58 | } 59 | 60 | if position, err = b.l.Compile(c); err != nil { 61 | return 62 | } 63 | if position, err = b.r.Compile(c); err != nil { 64 | return 65 | } 66 | position = c.Emit(code.OpBwLShift) 67 | c.Bookmark(b.pos) 68 | return 69 | } 70 | 71 | func (b BitwiseLeftShift) IsConstExpression() bool { 72 | return b.l.IsConstExpression() && b.r.IsConstExpression() 73 | } 74 | -------------------------------------------------------------------------------- /internal/ast/bitwiseshiftleftassign.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "github.com/NicoNex/tau/internal/compiler" 8 | "github.com/NicoNex/tau/internal/obj" 9 | ) 10 | 11 | type BitwiseShiftLeftAssign struct { 12 | l Node 13 | r Node 14 | pos int 15 | } 16 | 17 | func NewBitwiseShiftLeftAssign(l, r Node, pos int) Node { 18 | return BitwiseShiftLeftAssign{ 19 | l: l, 20 | r: r, 21 | pos: pos, 22 | } 23 | } 24 | 25 | func (b BitwiseShiftLeftAssign) Eval() (obj.Object, error) { 26 | return obj.NullObj, errors.New("ast.BitwiseShiftLeftAssign: not a constant expression") 27 | } 28 | 29 | func (b BitwiseShiftLeftAssign) String() string { 30 | return fmt.Sprintf("(%v << %v)", b.l, b.r) 31 | } 32 | 33 | func (b BitwiseShiftLeftAssign) Compile(c *compiler.Compiler) (position int, err error) { 34 | n := Assign{l: b.l, r: BitwiseLeftShift{l: b.l, r: b.r, pos: b.pos}, pos: b.pos} 35 | position, err = n.Compile(c) 36 | c.Bookmark(n.pos) 37 | return 38 | } 39 | 40 | func (b BitwiseShiftLeftAssign) IsConstExpression() bool { 41 | return false 42 | } 43 | -------------------------------------------------------------------------------- /internal/ast/bitwiseshiftright.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/NicoNex/tau/internal/code" 7 | "github.com/NicoNex/tau/internal/compiler" 8 | "github.com/NicoNex/tau/internal/obj" 9 | ) 10 | 11 | type BitwiseRightShift struct { 12 | l Node 13 | r Node 14 | pos int 15 | } 16 | 17 | func NewBitwiseRightShift(l, r Node, pos int) Node { 18 | return BitwiseRightShift{ 19 | l: l, 20 | r: r, 21 | pos: pos, 22 | } 23 | } 24 | 25 | func (b BitwiseRightShift) Eval() (obj.Object, error) { 26 | left, err := b.l.Eval() 27 | if err != nil { 28 | return obj.NullObj, err 29 | } 30 | 31 | right, err := b.r.Eval() 32 | if err != nil { 33 | return obj.NullObj, err 34 | } 35 | 36 | if !obj.AssertTypes(left, obj.IntType) { 37 | return obj.NullObj, fmt.Errorf("unsupported operator '>>' for type %v", left.Type()) 38 | } 39 | if !obj.AssertTypes(right, obj.IntType) { 40 | return obj.NullObj, fmt.Errorf("unsupported operator '>>' for type %v", right.Type()) 41 | } 42 | return obj.NewInteger(left.Int() >> right.Int()), nil 43 | } 44 | 45 | func (b BitwiseRightShift) String() string { 46 | return fmt.Sprintf("(%v >> %v)", b.l, b.r) 47 | } 48 | 49 | func (b BitwiseRightShift) Compile(c *compiler.Compiler) (position int, err error) { 50 | if b.IsConstExpression() { 51 | o, err := b.Eval() 52 | if err != nil { 53 | return 0, c.NewError(b.pos, err.Error()) 54 | } 55 | position = c.Emit(code.OpConstant, c.AddConstant(o)) 56 | c.Bookmark(b.pos) 57 | return position, err 58 | } 59 | 60 | if position, err = b.l.Compile(c); err != nil { 61 | return 62 | } 63 | if position, err = b.r.Compile(c); err != nil { 64 | return 65 | } 66 | position = c.Emit(code.OpBwRShift) 67 | c.Bookmark(b.pos) 68 | return 69 | } 70 | 71 | func (b BitwiseRightShift) IsConstExpression() bool { 72 | return b.l.IsConstExpression() && b.r.IsConstExpression() 73 | } 74 | -------------------------------------------------------------------------------- /internal/ast/bitwiseshiftrightassign.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "github.com/NicoNex/tau/internal/compiler" 8 | "github.com/NicoNex/tau/internal/obj" 9 | ) 10 | 11 | type BitwiseShiftRightAssign struct { 12 | l Node 13 | r Node 14 | pos int 15 | } 16 | 17 | func NewBitwiseShiftRightAssign(l, r Node, pos int) Node { 18 | return BitwiseShiftRightAssign{ 19 | l: l, 20 | r: r, 21 | pos: pos, 22 | } 23 | } 24 | 25 | func (b BitwiseShiftRightAssign) Eval() (obj.Object, error) { 26 | return obj.NullObj, errors.New("ast.BitwiseShiftRightAssign: not a constant expression") 27 | } 28 | 29 | func (b BitwiseShiftRightAssign) String() string { 30 | return fmt.Sprintf("(%v >> %v)", b.l, b.r) 31 | } 32 | 33 | func (b BitwiseShiftRightAssign) Compile(c *compiler.Compiler) (position int, err error) { 34 | n := Assign{l: b.l, r: BitwiseRightShift{l: b.l, r: b.r, pos: b.pos}, pos: b.pos} 35 | position, err = n.Compile(c) 36 | c.Bookmark(n.pos) 37 | return 38 | } 39 | 40 | func (b BitwiseShiftRightAssign) IsConstExpression() bool { 41 | return false 42 | } 43 | -------------------------------------------------------------------------------- /internal/ast/bitwisexor.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/NicoNex/tau/internal/code" 7 | "github.com/NicoNex/tau/internal/compiler" 8 | "github.com/NicoNex/tau/internal/obj" 9 | ) 10 | 11 | type BitwiseXor struct { 12 | l Node 13 | r Node 14 | pos int 15 | } 16 | 17 | func NewBitwiseXor(l, r Node, pos int) Node { 18 | return BitwiseXor{ 19 | l: l, 20 | r: r, 21 | pos: pos, 22 | } 23 | } 24 | 25 | func (b BitwiseXor) Eval() (obj.Object, error) { 26 | left, err := b.l.Eval() 27 | if err != nil { 28 | return obj.NullObj, err 29 | } 30 | 31 | right, err := b.r.Eval() 32 | if err != nil { 33 | return obj.NullObj, err 34 | } 35 | 36 | if !obj.AssertTypes(left, obj.IntType) { 37 | return obj.NullObj, fmt.Errorf("unsupported operator '^' for type %v", left.Type()) 38 | } 39 | if !obj.AssertTypes(right, obj.IntType) { 40 | return obj.NullObj, fmt.Errorf("unsupported operator '^' for type %v", right.Type()) 41 | } 42 | return obj.NewInteger(left.Int() ^ right.Int()), nil 43 | } 44 | 45 | func (b BitwiseXor) String() string { 46 | return fmt.Sprintf("(%v ^ %v)", b.l, b.r) 47 | } 48 | 49 | func (b BitwiseXor) Compile(c *compiler.Compiler) (position int, err error) { 50 | if b.IsConstExpression() { 51 | o, err := b.Eval() 52 | if err != nil { 53 | return 0, c.NewError(b.pos, err.Error()) 54 | } 55 | position = c.Emit(code.OpConstant, c.AddConstant(o)) 56 | c.Bookmark(b.pos) 57 | return position, err 58 | } 59 | 60 | if position, err = b.l.Compile(c); err != nil { 61 | return 62 | } 63 | if position, err = b.r.Compile(c); err != nil { 64 | return 65 | } 66 | position = c.Emit(code.OpBwXor) 67 | c.Bookmark(b.pos) 68 | return 69 | } 70 | 71 | func (b BitwiseXor) IsConstExpression() bool { 72 | return b.l.IsConstExpression() && b.r.IsConstExpression() 73 | } 74 | -------------------------------------------------------------------------------- /internal/ast/bitwisexorassign.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "github.com/NicoNex/tau/internal/compiler" 8 | "github.com/NicoNex/tau/internal/obj" 9 | ) 10 | 11 | type BitwiseXorAssign struct { 12 | l Node 13 | r Node 14 | pos int 15 | } 16 | 17 | func NewBitwiseXorAssign(l, r Node, pos int) Node { 18 | return BitwiseXorAssign{ 19 | l: l, 20 | r: r, 21 | pos: pos, 22 | } 23 | } 24 | 25 | func (b BitwiseXorAssign) Eval() (obj.Object, error) { 26 | return obj.NullObj, errors.New("ast.BitwiseXorAssign: not a constant expression") 27 | } 28 | 29 | func (b BitwiseXorAssign) String() string { 30 | return fmt.Sprintf("(%v ^= %v)", b.l, b.r) 31 | } 32 | 33 | func (b BitwiseXorAssign) Compile(c *compiler.Compiler) (position int, err error) { 34 | n := Assign{l: b.l, r: BitwiseXor{l: b.l, r: b.r, pos: b.pos}, pos: b.pos} 35 | position, err = n.Compile(c) 36 | c.Bookmark(n.pos) 37 | return 38 | } 39 | 40 | func (b BitwiseXorAssign) IsConstExpression() bool { 41 | return false 42 | } 43 | -------------------------------------------------------------------------------- /internal/ast/block.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "errors" 5 | "strings" 6 | 7 | "github.com/NicoNex/tau/internal/code" 8 | "github.com/NicoNex/tau/internal/compiler" 9 | "github.com/NicoNex/tau/internal/obj" 10 | ) 11 | 12 | type Block []Node 13 | 14 | func NewBlock() Block { 15 | return Block([]Node{}) 16 | } 17 | 18 | func (b Block) Eval() (obj.Object, error) { 19 | return obj.NullObj, errors.New("ast.Block: not a constant expression") 20 | } 21 | 22 | func (b Block) String() string { 23 | var nodes []string 24 | 25 | for _, n := range b { 26 | nodes = append(nodes, n.String()) 27 | } 28 | return strings.Join(nodes, "; ") 29 | } 30 | 31 | func (b *Block) Add(n Node) { 32 | *b = append(*b, n) 33 | } 34 | 35 | func (b Block) Compile(c *compiler.Compiler) (position int, err error) { 36 | for _, n := range b { 37 | if position, err = n.Compile(c); err != nil { 38 | return 39 | } 40 | 41 | if _, isReturn := n.(Return); !isReturn { 42 | position = c.Emit(code.OpPop) 43 | } 44 | } 45 | return 46 | } 47 | 48 | func (b Block) IsConstExpression() bool { 49 | return false 50 | } 51 | -------------------------------------------------------------------------------- /internal/ast/boolean.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "github.com/NicoNex/tau/internal/code" 5 | "github.com/NicoNex/tau/internal/compiler" 6 | "github.com/NicoNex/tau/internal/obj" 7 | ) 8 | 9 | type Boolean bool 10 | 11 | func NewBoolean(b bool) Node { 12 | return Boolean(b) 13 | } 14 | 15 | func (b Boolean) Eval() (obj.Object, error) { 16 | return obj.ParseBool(bool(b)), nil 17 | } 18 | 19 | func (b Boolean) String() string { 20 | if b { 21 | return "true" 22 | } 23 | return "false" 24 | } 25 | 26 | func (b Boolean) Compile(c *compiler.Compiler) (position int, err error) { 27 | if bool(b) { 28 | return c.Emit(code.OpTrue), nil 29 | } else { 30 | return c.Emit(code.OpFalse), nil 31 | } 32 | } 33 | 34 | func (b Boolean) IsConstExpression() bool { 35 | return true 36 | } 37 | -------------------------------------------------------------------------------- /internal/ast/break.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/NicoNex/tau/internal/code" 7 | "github.com/NicoNex/tau/internal/compiler" 8 | "github.com/NicoNex/tau/internal/obj" 9 | ) 10 | 11 | type Break struct{} 12 | 13 | func NewBreak() Break { 14 | return Break{} 15 | } 16 | 17 | func (b Break) Eval() (obj.Object, error) { 18 | return obj.NullObj, errors.New("ast.Break: not a constant expression") 19 | } 20 | 21 | func (b Break) String() string { 22 | return "break" 23 | } 24 | 25 | func (b Break) Compile(c *compiler.Compiler) (position int, err error) { 26 | return c.Emit(code.OpJump, compiler.BreakPlaceholder), nil 27 | } 28 | 29 | func (b Break) IsConstExpression() bool { 30 | return false 31 | } 32 | -------------------------------------------------------------------------------- /internal/ast/call.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "strings" 7 | 8 | "github.com/NicoNex/tau/internal/code" 9 | "github.com/NicoNex/tau/internal/compiler" 10 | "github.com/NicoNex/tau/internal/obj" 11 | ) 12 | 13 | type Call struct { 14 | Fn Node 15 | Args []Node 16 | pos int 17 | } 18 | 19 | func NewCall(fn Node, args []Node, pos int) Node { 20 | return Call{ 21 | Fn: fn, 22 | Args: args, 23 | pos: pos, 24 | } 25 | } 26 | 27 | func (c Call) Eval() (obj.Object, error) { 28 | return obj.NullObj, errors.New("ast.Call: not a constant expression") 29 | } 30 | 31 | func (c Call) String() string { 32 | var args []string 33 | 34 | for _, a := range c.Args { 35 | args = append(args, a.String()) 36 | } 37 | return fmt.Sprintf("%v(%s)", c.Fn, strings.Join(args, ", ")) 38 | } 39 | 40 | func (c Call) Compile(comp *compiler.Compiler) (position int, err error) { 41 | if position, err = c.Fn.Compile(comp); err != nil { 42 | return 43 | } 44 | 45 | for _, a := range c.Args { 46 | if position, err = a.Compile(comp); err != nil { 47 | return 48 | } 49 | } 50 | 51 | position = comp.Emit(code.OpCall, len(c.Args)) 52 | comp.Bookmark(c.pos) 53 | return 54 | } 55 | 56 | func (c Call) IsConstExpression() bool { 57 | return false 58 | } 59 | -------------------------------------------------------------------------------- /internal/ast/concurrentcall.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "strings" 7 | 8 | "github.com/NicoNex/tau/internal/code" 9 | "github.com/NicoNex/tau/internal/compiler" 10 | "github.com/NicoNex/tau/internal/obj" 11 | ) 12 | 13 | type ConcurrentCall struct { 14 | fn Node 15 | args []Node 16 | } 17 | 18 | func NewConcurrentCall(fn Node, args []Node) Node { 19 | return ConcurrentCall{fn, args} 20 | } 21 | 22 | func (c ConcurrentCall) Eval() (obj.Object, error) { 23 | return obj.NullObj, errors.New("ast.ConcurrentCall: not a constant expression") 24 | } 25 | 26 | func (c ConcurrentCall) String() string { 27 | var args = make([]string, len(c.args)) 28 | 29 | for i, a := range c.args { 30 | args[i] = a.String() 31 | } 32 | return fmt.Sprintf("tau %v(%s)", c.fn, strings.Join(args, ", ")) 33 | } 34 | 35 | func (c ConcurrentCall) Compile(comp *compiler.Compiler) (position int, err error) { 36 | if position, err = c.fn.Compile(comp); err != nil { 37 | return 38 | } 39 | 40 | for _, a := range c.args { 41 | if position, err = a.Compile(comp); err != nil { 42 | return 43 | } 44 | } 45 | 46 | return comp.Emit(code.OpConcurrentCall, len(c.args)), nil 47 | } 48 | 49 | func (ConcurrentCall) IsConstExpression() bool { 50 | return false 51 | } 52 | -------------------------------------------------------------------------------- /internal/ast/continue.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/NicoNex/tau/internal/code" 7 | "github.com/NicoNex/tau/internal/compiler" 8 | "github.com/NicoNex/tau/internal/obj" 9 | ) 10 | 11 | type Continue struct{} 12 | 13 | func NewContinue() Continue { 14 | return Continue{} 15 | } 16 | 17 | func (c Continue) Eval() (obj.Object, error) { 18 | return obj.NullObj, errors.New("ast.ConcurrentCall: not a constant expression") 19 | } 20 | 21 | func (c Continue) String() string { 22 | return "break" 23 | } 24 | 25 | func (c Continue) Compile(comp *compiler.Compiler) (position int, err error) { 26 | return comp.Emit(code.OpJump, compiler.ContinuePlaceholder), nil 27 | } 28 | 29 | func (c Continue) IsConstExpression() bool { 30 | return false 31 | } 32 | -------------------------------------------------------------------------------- /internal/ast/divide.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/NicoNex/tau/internal/code" 7 | "github.com/NicoNex/tau/internal/compiler" 8 | "github.com/NicoNex/tau/internal/obj" 9 | ) 10 | 11 | type Divide struct { 12 | l Node 13 | r Node 14 | pos int 15 | } 16 | 17 | func NewDivide(l, r Node, pos int) Node { 18 | return Divide{ 19 | l: l, 20 | r: r, 21 | pos: pos, 22 | } 23 | } 24 | 25 | func (d Divide) Eval() (obj.Object, error) { 26 | 27 | left, err := d.l.Eval() 28 | if err != nil { 29 | return obj.NullObj, err 30 | } 31 | 32 | right, err := d.r.Eval() 33 | if err != nil { 34 | return obj.NullObj, err 35 | } 36 | 37 | if !obj.AssertTypes(left, obj.IntType, obj.FloatType) { 38 | return obj.NullObj, fmt.Errorf("unsupported operator '/' for type %v", left.Type()) 39 | } 40 | if !obj.AssertTypes(right, obj.IntType, obj.FloatType) { 41 | return obj.NullObj, fmt.Errorf("unsupported operator '/' for type %v", right.Type()) 42 | } 43 | 44 | l, r := obj.ToFloat(left, right) 45 | return obj.NewFloat(l / r), nil 46 | } 47 | 48 | func (d Divide) String() string { 49 | return fmt.Sprintf("(%v / %v)", d.l, d.r) 50 | } 51 | 52 | func (d Divide) Compile(c *compiler.Compiler) (position int, err error) { 53 | if d.IsConstExpression() { 54 | o, err := d.Eval() 55 | if err != nil { 56 | return 0, c.NewError(d.pos, err.Error()) 57 | } 58 | position = c.Emit(code.OpConstant, c.AddConstant(o)) 59 | c.Bookmark(d.pos) 60 | return position, err 61 | } 62 | 63 | if position, err = d.l.Compile(c); err != nil { 64 | return 65 | } 66 | if position, err = d.r.Compile(c); err != nil { 67 | return 68 | } 69 | position = c.Emit(code.OpDiv) 70 | c.Bookmark(d.pos) 71 | return 72 | } 73 | 74 | func (d Divide) IsConstExpression() bool { 75 | return d.l.IsConstExpression() && d.r.IsConstExpression() 76 | } 77 | -------------------------------------------------------------------------------- /internal/ast/divideassign.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "github.com/NicoNex/tau/internal/compiler" 8 | "github.com/NicoNex/tau/internal/obj" 9 | ) 10 | 11 | type DivideAssign struct { 12 | l Node 13 | r Node 14 | pos int 15 | } 16 | 17 | func NewDivideAssign(l, r Node, pos int) Node { 18 | return DivideAssign{ 19 | l: l, 20 | r: r, 21 | pos: pos, 22 | } 23 | } 24 | 25 | func (d DivideAssign) Eval() (obj.Object, error) { 26 | return obj.NullObj, errors.New("ast.DivideAssign: not a constant expression") 27 | } 28 | 29 | func (d DivideAssign) String() string { 30 | return fmt.Sprintf("(%v /= %v)", d.l, d.r) 31 | } 32 | 33 | func (d DivideAssign) Compile(c *compiler.Compiler) (position int, err error) { 34 | n := Assign{l: d.l, r: Divide{l: d.l, r: d.r, pos: d.pos}, pos: d.pos} 35 | position, err = n.Compile(c) 36 | c.Bookmark(n.pos) 37 | return 38 | } 39 | 40 | func (d DivideAssign) IsConstExpression() bool { 41 | return false 42 | } 43 | -------------------------------------------------------------------------------- /internal/ast/dot.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "github.com/NicoNex/tau/internal/code" 8 | "github.com/NicoNex/tau/internal/compiler" 9 | "github.com/NicoNex/tau/internal/obj" 10 | ) 11 | 12 | type Dot struct { 13 | l Node 14 | r Node 15 | pos int 16 | } 17 | 18 | func NewDot(l, r Node, pos int) Node { 19 | return Dot{ 20 | l: l, 21 | r: r, 22 | pos: pos, 23 | } 24 | } 25 | 26 | func (d Dot) Eval() (obj.Object, error) { 27 | return obj.NullObj, errors.New("ast.Dot: not a constant expression") 28 | } 29 | 30 | func (d Dot) String() string { 31 | return fmt.Sprintf("%v.%v", d.l, d.r) 32 | } 33 | 34 | func (d Dot) Compile(c *compiler.Compiler) (position int, err error) { 35 | if position, err = d.l.Compile(c); err != nil { 36 | return 37 | } 38 | if _, ok := d.r.(Identifier); !ok { 39 | return position, fmt.Errorf("expected identifier with dot operator, got %T", d.r) 40 | } 41 | position = c.Emit(code.OpConstant, c.AddConstant(obj.NewString(d.r.String()))) 42 | position = c.Emit(code.OpDot) 43 | c.Bookmark(d.pos) 44 | return 45 | } 46 | 47 | // CompileDefine assumes the dot operation is for defining a value. 48 | func (d Dot) CompileDefine(c *compiler.Compiler) (position int, err error) { 49 | if position, err = d.l.Compile(c); err != nil { 50 | return 51 | } 52 | if _, ok := d.r.(Identifier); !ok { 53 | return position, fmt.Errorf("expected identifier with dot operator, got %T", d.r) 54 | } 55 | position = c.Emit(code.OpConstant, c.AddConstant(obj.NewString(d.r.String()))) 56 | c.Bookmark(d.pos) 57 | return 58 | } 59 | 60 | func (d Dot) IsConstExpression() bool { 61 | return false 62 | } 63 | -------------------------------------------------------------------------------- /internal/ast/equals.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/NicoNex/tau/internal/code" 7 | "github.com/NicoNex/tau/internal/compiler" 8 | "github.com/NicoNex/tau/internal/obj" 9 | ) 10 | 11 | type Equals struct { 12 | l Node 13 | r Node 14 | pos int 15 | } 16 | 17 | func NewEquals(l, r Node, pos int) Node { 18 | return Equals{ 19 | l: l, 20 | r: r, 21 | pos: pos, 22 | } 23 | } 24 | 25 | func (e Equals) Eval() (obj.Object, error) { 26 | left, err := e.l.Eval() 27 | if err != nil { 28 | return obj.NullObj, err 29 | } 30 | 31 | right, err := e.r.Eval() 32 | if err != nil { 33 | return obj.NullObj, err 34 | } 35 | 36 | if !obj.AssertTypes(left, obj.IntType, obj.FloatType, obj.StringType, obj.BoolType, obj.NullType) { 37 | return obj.NullObj, fmt.Errorf("unsupported operator '==' for type %v", left.Type()) 38 | } 39 | if !obj.AssertTypes(right, obj.IntType, obj.FloatType, obj.StringType, obj.BoolType, obj.NullType) { 40 | return obj.NullObj, fmt.Errorf("unsupported operator '==' for type %v", right.Type()) 41 | } 42 | 43 | switch { 44 | case obj.AssertTypes(left, obj.BoolType, obj.NullType) || obj.AssertTypes(right, obj.BoolType, obj.NullType): 45 | return obj.ParseBool(left.Type() == right.Type() && left.Int() == right.Int()), nil 46 | 47 | case obj.AssertTypes(left, obj.StringType) && obj.AssertTypes(right, obj.StringType): 48 | return obj.ParseBool(left.String() == right.String()), nil 49 | 50 | case obj.AssertTypes(left, obj.IntType) && obj.AssertTypes(right, obj.IntType): 51 | return obj.ParseBool(left.Int() == right.Int()), nil 52 | 53 | case obj.AssertTypes(left, obj.FloatType, obj.IntType) && obj.AssertTypes(right, obj.FloatType, obj.IntType): 54 | l, r := obj.ToFloat(left, right) 55 | return obj.ParseBool(l == r), nil 56 | 57 | default: 58 | return obj.FalseObj, nil 59 | } 60 | } 61 | 62 | func (e Equals) String() string { 63 | return fmt.Sprintf("(%v == %v)", e.l, e.r) 64 | } 65 | 66 | func (e Equals) Compile(c *compiler.Compiler) (position int, err error) { 67 | if e.IsConstExpression() { 68 | o, err := e.Eval() 69 | if err != nil { 70 | return 0, c.NewError(e.pos, err.Error()) 71 | } 72 | position = c.Emit(code.OpConstant, c.AddConstant(o)) 73 | c.Bookmark(e.pos) 74 | return position, err 75 | } 76 | 77 | if position, err = e.l.Compile(c); err != nil { 78 | return 79 | } 80 | if position, err = e.r.Compile(c); err != nil { 81 | return 82 | } 83 | position = c.Emit(code.OpEqual) 84 | c.Bookmark(e.pos) 85 | return 86 | } 87 | 88 | func (e Equals) IsConstExpression() bool { 89 | return e.l.IsConstExpression() && e.r.IsConstExpression() 90 | } 91 | -------------------------------------------------------------------------------- /internal/ast/float.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "strconv" 5 | 6 | "github.com/NicoNex/tau/internal/code" 7 | "github.com/NicoNex/tau/internal/compiler" 8 | "github.com/NicoNex/tau/internal/obj" 9 | ) 10 | 11 | type Float float64 12 | 13 | func NewFloat(f float64) Node { 14 | return Float(f) 15 | } 16 | 17 | func (f Float) Eval() (obj.Object, error) { 18 | return obj.NewFloat(float64(f)), nil 19 | } 20 | 21 | func (f Float) String() string { 22 | return strconv.FormatFloat(float64(f), 'f', -1, 64) 23 | } 24 | 25 | func (f Float) Compile(c *compiler.Compiler) (position int, err error) { 26 | return c.Emit(code.OpConstant, c.AddConstant(obj.NewFloat(float64(f)))), nil 27 | } 28 | 29 | func (f Float) IsConstExpression() bool { 30 | return true 31 | } 32 | -------------------------------------------------------------------------------- /internal/ast/for.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "github.com/NicoNex/tau/internal/code" 8 | "github.com/NicoNex/tau/internal/compiler" 9 | "github.com/NicoNex/tau/internal/obj" 10 | ) 11 | 12 | type For struct { 13 | before Node 14 | after Node 15 | cond Node 16 | body Node 17 | pos int 18 | } 19 | 20 | func NewFor(cond, body, before, after Node, pos int) Node { 21 | return For{ 22 | before: before, 23 | after: after, 24 | cond: cond, 25 | body: body, 26 | pos: pos, 27 | } 28 | } 29 | 30 | func (f For) Eval() (obj.Object, error) { 31 | return obj.NullObj, errors.New("ast.For: not a constant expression") 32 | } 33 | 34 | func (f For) String() string { 35 | return fmt.Sprintf("for %v { %v }", f.cond, f.body) 36 | } 37 | 38 | func (f For) Compile(c *compiler.Compiler) (position int, err error) { 39 | if f.before != nil { 40 | if position, err = f.before.Compile(c); err != nil { 41 | return 42 | } 43 | } 44 | 45 | startPos := c.Pos() 46 | if position, err = f.cond.Compile(c); err != nil { 47 | return 48 | } 49 | 50 | jumpNotTruthyPos := c.Emit(code.OpJumpNotTruthy, compiler.GenericPlaceholder) 51 | 52 | startBody := c.Pos() 53 | if position, err = f.body.Compile(c); err != nil { 54 | return 55 | } 56 | endBody := c.Pos() 57 | 58 | if f.after != nil { 59 | if position, err = f.after.Compile(c); err != nil { 60 | return 61 | } 62 | c.Emit(code.OpPop) 63 | } 64 | 65 | c.Emit(code.OpJump, startPos) 66 | endPos := c.Emit(code.OpNull) 67 | c.ReplaceOperand(jumpNotTruthyPos, endPos) 68 | 69 | err = c.ReplaceContinueOperands(startBody, endBody, endBody) 70 | if err != nil { 71 | return 72 | } 73 | err = c.ReplaceBreakOperands(startBody, endBody, endPos) 74 | if err != nil { 75 | return 76 | } 77 | 78 | c.Bookmark(f.pos) 79 | return endPos, nil 80 | } 81 | 82 | func (f For) IsConstExpression() bool { 83 | return false 84 | } 85 | -------------------------------------------------------------------------------- /internal/ast/function.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "strings" 7 | 8 | "github.com/NicoNex/tau/internal/code" 9 | "github.com/NicoNex/tau/internal/compiler" 10 | "github.com/NicoNex/tau/internal/obj" 11 | ) 12 | 13 | type Function struct { 14 | body Node 15 | Name string 16 | pos int 17 | params []Identifier 18 | } 19 | 20 | func NewFunction(params []Identifier, body Node, pos int) Node { 21 | return Function{ 22 | params: params, 23 | body: body, 24 | pos: pos, 25 | } 26 | } 27 | 28 | func (f Function) Eval() (obj.Object, error) { 29 | return obj.NullObj, errors.New("ast.Function: not a constant expression") 30 | } 31 | 32 | func (f Function) String() string { 33 | var params []string 34 | 35 | for _, p := range f.params { 36 | params = append(params, p.String()) 37 | } 38 | return fmt.Sprintf("fn(%s) { %v }", strings.Join(params, ", "), f.body) 39 | } 40 | 41 | func (f Function) Compile(c *compiler.Compiler) (position int, err error) { 42 | c.EnterScope() 43 | 44 | if f.Name != "" { 45 | c.DefineFunctionName(f.Name) 46 | } 47 | 48 | for _, p := range f.params { 49 | c.Define(p.String()) 50 | } 51 | 52 | if position, err = f.body.Compile(c); err != nil { 53 | return 54 | } 55 | 56 | if c.LastIs(code.OpPop) { 57 | c.ReplaceLastPopWithReturn() 58 | } 59 | if !c.LastIs(code.OpReturnValue) { 60 | c.Emit(code.OpReturn) 61 | } 62 | 63 | freeSymbols := c.FreeSymbols 64 | nLocals := c.NumDefs 65 | ins, bookmarks := c.LeaveScope() 66 | 67 | for _, s := range freeSymbols { 68 | position = c.LoadSymbol(s) 69 | } 70 | 71 | fn := obj.NewFunctionCompiled(ins, nLocals, len(f.params), bookmarks) 72 | position = c.Emit(code.OpClosure, c.AddConstant(fn), len(freeSymbols)) 73 | c.Bookmark(f.pos) 74 | return 75 | } 76 | 77 | func (f Function) IsConstExpression() bool { 78 | return false 79 | } 80 | -------------------------------------------------------------------------------- /internal/ast/greater.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/NicoNex/tau/internal/code" 7 | "github.com/NicoNex/tau/internal/compiler" 8 | "github.com/NicoNex/tau/internal/obj" 9 | ) 10 | 11 | type Greater struct { 12 | l Node 13 | r Node 14 | pos int 15 | } 16 | 17 | func NewGreater(l, r Node, pos int) Node { 18 | return Greater{ 19 | l: l, 20 | r: r, 21 | pos: pos, 22 | } 23 | } 24 | 25 | func (g Greater) Eval() (obj.Object, error) { 26 | left, err := g.l.Eval() 27 | if err != nil { 28 | return obj.NullObj, err 29 | } 30 | 31 | right, err := g.r.Eval() 32 | if err != nil { 33 | return obj.NullObj, err 34 | } 35 | 36 | switch { 37 | case obj.AssertTypes(left, obj.IntType) && obj.AssertTypes(right, obj.IntType): 38 | return obj.ParseBool(left.Int() > right.Int()), nil 39 | 40 | case obj.AssertTypes(left, obj.IntType, obj.FloatType) && obj.AssertTypes(right, obj.IntType, obj.FloatType): 41 | l, r := obj.ToFloat(left, right) 42 | return obj.ParseBool(l > r), nil 43 | 44 | case obj.AssertTypes(left, obj.StringType) && obj.AssertTypes(right, obj.StringType): 45 | return obj.ParseBool(left.String() > right.String()), nil 46 | 47 | default: 48 | return obj.NullObj, fmt.Errorf("unsupported operator '>' for types %v and %v", left.Type(), right.Type()) 49 | } 50 | } 51 | 52 | func (g Greater) String() string { 53 | return fmt.Sprintf("(%v > %v)", g.l, g.r) 54 | } 55 | 56 | func (g Greater) Compile(c *compiler.Compiler) (position int, err error) { 57 | if g.IsConstExpression() { 58 | o, err := g.Eval() 59 | if err != nil { 60 | return 0, c.NewError(g.pos, err.Error()) 61 | } 62 | position = c.Emit(code.OpConstant, c.AddConstant(o)) 63 | c.Bookmark(g.pos) 64 | return position, err 65 | } 66 | 67 | if position, err = g.l.Compile(c); err != nil { 68 | return 69 | } 70 | if position, err = g.r.Compile(c); err != nil { 71 | return 72 | } 73 | position = c.Emit(code.OpGreaterThan) 74 | c.Bookmark(g.pos) 75 | return 76 | } 77 | 78 | func (g Greater) IsConstExpression() bool { 79 | return g.l.IsConstExpression() && g.r.IsConstExpression() 80 | } 81 | -------------------------------------------------------------------------------- /internal/ast/greatereq.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/NicoNex/tau/internal/code" 7 | "github.com/NicoNex/tau/internal/compiler" 8 | "github.com/NicoNex/tau/internal/obj" 9 | ) 10 | 11 | type GreaterEq struct { 12 | l Node 13 | r Node 14 | pos int 15 | } 16 | 17 | func NewGreaterEq(l, r Node, pos int) Node { 18 | return GreaterEq{ 19 | l: l, 20 | r: r, 21 | pos: pos, 22 | } 23 | } 24 | 25 | func (g GreaterEq) Eval() (obj.Object, error) { 26 | left, err := g.l.Eval() 27 | if err != nil { 28 | return obj.NullObj, err 29 | } 30 | 31 | right, err := g.r.Eval() 32 | if err != nil { 33 | return obj.NullObj, err 34 | } 35 | 36 | switch { 37 | case obj.AssertTypes(left, obj.IntType) && obj.AssertTypes(right, obj.IntType): 38 | return obj.ParseBool(left.Int() >= right.Int()), nil 39 | 40 | case obj.AssertTypes(left, obj.IntType, obj.FloatType) && obj.AssertTypes(right, obj.IntType, obj.FloatType): 41 | l, r := obj.ToFloat(left, right) 42 | return obj.ParseBool(l >= r), nil 43 | 44 | case obj.AssertTypes(left, obj.StringType) && obj.AssertTypes(right, obj.StringType): 45 | return obj.ParseBool(left.String() >= right.String()), nil 46 | 47 | default: 48 | return obj.NullObj, fmt.Errorf("unsupported operator '>=' for types %v and %v", left.Type(), right.Type()) 49 | } 50 | } 51 | 52 | func (g GreaterEq) String() string { 53 | return fmt.Sprintf("(%v >= %v)", g.l, g.r) 54 | } 55 | 56 | func (g GreaterEq) Compile(c *compiler.Compiler) (position int, err error) { 57 | if g.IsConstExpression() { 58 | o, err := g.Eval() 59 | if err != nil { 60 | return 0, c.NewError(g.pos, err.Error()) 61 | } 62 | position = c.Emit(code.OpConstant, c.AddConstant(o)) 63 | c.Bookmark(g.pos) 64 | return position, err 65 | } 66 | 67 | if position, err = g.l.Compile(c); err != nil { 68 | return 69 | } 70 | if position, err = g.r.Compile(c); err != nil { 71 | return 72 | } 73 | position = c.Emit(code.OpGreaterThanEqual) 74 | c.Bookmark(g.pos) 75 | return 76 | } 77 | 78 | func (g GreaterEq) IsConstExpression() bool { 79 | return g.l.IsConstExpression() && g.r.IsConstExpression() 80 | } 81 | -------------------------------------------------------------------------------- /internal/ast/identifier.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/NicoNex/tau/internal/compiler" 7 | "github.com/NicoNex/tau/internal/obj" 8 | ) 9 | 10 | type Identifier struct { 11 | name string 12 | pos int 13 | } 14 | 15 | func NewIdentifier(name string, pos int) Identifier { 16 | return Identifier{ 17 | name: name, 18 | pos: pos, 19 | } 20 | } 21 | 22 | func (i Identifier) Eval() (obj.Object, error) { 23 | return obj.NullObj, errors.New("ast.Identifier: not a constant expression") 24 | } 25 | 26 | func (i Identifier) String() string { 27 | return i.name 28 | } 29 | 30 | func (i Identifier) Compile(c *compiler.Compiler) (position int, err error) { 31 | if symbol, ok := c.Resolve(i.name); ok { 32 | return c.LoadSymbol(symbol), nil 33 | } 34 | return 0, c.UnresolvedError(i.name, i.pos) 35 | } 36 | 37 | func (i Identifier) IsConstExpression() bool { 38 | return false 39 | } 40 | -------------------------------------------------------------------------------- /internal/ast/ifelse.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "github.com/NicoNex/tau/internal/code" 8 | "github.com/NicoNex/tau/internal/compiler" 9 | "github.com/NicoNex/tau/internal/obj" 10 | ) 11 | 12 | type IfExpr struct { 13 | cond Node 14 | body Node 15 | altern Node 16 | pos int 17 | } 18 | 19 | func NewIfExpr(cond, body, alt Node, pos int) Node { 20 | return IfExpr{ 21 | cond: cond, 22 | body: body, 23 | altern: alt, 24 | pos: pos, 25 | } 26 | } 27 | 28 | func (i IfExpr) Eval() (obj.Object, error) { 29 | return obj.NullObj, errors.New("ast.IfExpr: not a constant expression") 30 | } 31 | 32 | func (i IfExpr) String() string { 33 | if i.altern != nil { 34 | return fmt.Sprintf("if %v { %v } else { %v }", i.cond, i.body, i.altern) 35 | } 36 | return fmt.Sprintf("if %v { %v }", i.cond, i.body) 37 | } 38 | 39 | func (i IfExpr) Compile(c *compiler.Compiler) (position int, err error) { 40 | if position, err = i.cond.Compile(c); err != nil { 41 | return 42 | } 43 | jumpNotTruthyPos := c.Emit(code.OpJumpNotTruthy, compiler.GenericPlaceholder) 44 | if position, err = i.body.Compile(c); err != nil { 45 | return 46 | } 47 | 48 | if c.LastIs(code.OpPop) { 49 | c.RemoveLast() 50 | } 51 | 52 | jumpPos := c.Emit(code.OpJump, compiler.GenericPlaceholder) 53 | c.ReplaceOperand(jumpNotTruthyPos, c.Pos()) 54 | 55 | if i.altern == nil { 56 | c.Emit(code.OpNull) 57 | } else { 58 | if position, err = i.altern.Compile(c); err != nil { 59 | return 60 | } 61 | 62 | if c.LastIs(code.OpPop) { 63 | c.RemoveLast() 64 | } 65 | } 66 | 67 | c.ReplaceOperand(jumpPos, c.Pos()) 68 | return c.Pos(), nil 69 | } 70 | 71 | func (i IfExpr) IsConstExpression() bool { 72 | return false 73 | } 74 | -------------------------------------------------------------------------------- /internal/ast/import.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "github.com/NicoNex/tau/internal/code" 8 | "github.com/NicoNex/tau/internal/compiler" 9 | "github.com/NicoNex/tau/internal/obj" 10 | ) 11 | 12 | type Import struct { 13 | name Node 14 | parse parseFn 15 | pos int 16 | } 17 | 18 | func NewImport(name Node, parse parseFn, pos int) Node { 19 | return &Import{ 20 | name: name, 21 | parse: parse, 22 | pos: pos, 23 | } 24 | } 25 | 26 | func (i Import) Eval() (obj.Object, error) { 27 | return obj.NullObj, errors.New("ast.Import: not a constant expression") 28 | } 29 | 30 | func (i Import) Compile(c *compiler.Compiler) (position int, err error) { 31 | if position, err = i.name.Compile(c); err != nil { 32 | return 33 | } 34 | position = c.Emit(code.OpLoadModule) 35 | c.Bookmark(i.pos) 36 | return 37 | } 38 | 39 | func (i Import) String() string { 40 | return fmt.Sprintf("import(%q)", i.name.String()) 41 | } 42 | 43 | func (i Import) IsConstExpression() bool { 44 | return false 45 | } 46 | -------------------------------------------------------------------------------- /internal/ast/index.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "github.com/NicoNex/tau/internal/code" 8 | "github.com/NicoNex/tau/internal/compiler" 9 | "github.com/NicoNex/tau/internal/obj" 10 | ) 11 | 12 | type Index struct { 13 | left Node 14 | index Node 15 | pos int 16 | } 17 | 18 | func NewIndex(l, i Node, pos int) Node { 19 | return Index{left: l, 20 | index: i, 21 | pos: pos, 22 | } 23 | } 24 | 25 | func (i Index) Eval() (obj.Object, error) { 26 | return obj.NullObj, errors.New("ast.Index: not a constant expression") 27 | } 28 | 29 | func (i Index) String() string { 30 | return fmt.Sprintf("%v[%v]", i.left, i.index) 31 | } 32 | 33 | func (i Index) Compile(c *compiler.Compiler) (position int, err error) { 34 | if position, err = i.left.Compile(c); err != nil { 35 | return 36 | } 37 | if position, err = i.index.Compile(c); err != nil { 38 | return 39 | } 40 | position = c.Emit(code.OpIndex) 41 | c.Bookmark(i.pos) 42 | return 43 | } 44 | 45 | // CompileDefine assumes the index operation is for defining a value. 46 | func (i Index) CompileDefine(c *compiler.Compiler) (position int, err error) { 47 | if position, err = i.left.Compile(c); err != nil { 48 | return 49 | } 50 | if position, err = i.index.Compile(c); err != nil { 51 | return 52 | } 53 | c.Bookmark(i.pos) 54 | return 55 | } 56 | 57 | func (i Index) IsConstExpression() bool { 58 | return false 59 | } 60 | -------------------------------------------------------------------------------- /internal/ast/integer.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "strconv" 5 | 6 | "github.com/NicoNex/tau/internal/code" 7 | "github.com/NicoNex/tau/internal/compiler" 8 | "github.com/NicoNex/tau/internal/obj" 9 | ) 10 | 11 | type Integer int64 12 | 13 | func NewInteger(i int64) Node { 14 | return Integer(i) 15 | } 16 | 17 | func (i Integer) Eval() (obj.Object, error) { 18 | return obj.NewInteger(int64(i)), nil 19 | } 20 | 21 | func (i Integer) String() string { 22 | return strconv.FormatInt(int64(i), 10) 23 | } 24 | 25 | func (i Integer) Compile(c *compiler.Compiler) (position int, err error) { 26 | return c.Emit(code.OpConstant, c.AddConstant(obj.NewInteger(int64(i)))), nil 27 | } 28 | 29 | func (i Integer) IsConstExpression() bool { 30 | return true 31 | } 32 | -------------------------------------------------------------------------------- /internal/ast/less.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/NicoNex/tau/internal/code" 7 | "github.com/NicoNex/tau/internal/compiler" 8 | "github.com/NicoNex/tau/internal/obj" 9 | ) 10 | 11 | type Less struct { 12 | l Node 13 | r Node 14 | pos int 15 | } 16 | 17 | func NewLess(l, r Node, pos int) Node { 18 | return Less{ 19 | l: l, 20 | r: r, 21 | pos: pos, 22 | } 23 | } 24 | 25 | func (l Less) Eval() (obj.Object, error) { 26 | left, err := l.l.Eval() 27 | if err != nil { 28 | return obj.NullObj, err 29 | } 30 | 31 | right, err := l.r.Eval() 32 | if err != nil { 33 | return obj.NullObj, err 34 | } 35 | 36 | switch { 37 | case obj.AssertTypes(left, obj.IntType) && obj.AssertTypes(right, obj.IntType): 38 | return obj.ParseBool(left.Int() < right.Int()), nil 39 | 40 | case obj.AssertTypes(left, obj.IntType, obj.FloatType) && obj.AssertTypes(right, obj.IntType, obj.FloatType): 41 | l, r := obj.ToFloat(left, right) 42 | return obj.ParseBool(l < r), nil 43 | 44 | case obj.AssertTypes(left, obj.StringType) && obj.AssertTypes(right, obj.StringType): 45 | return obj.ParseBool(left.String() < right.String()), nil 46 | 47 | default: 48 | return obj.NullObj, fmt.Errorf("unsupported operator '<' for types %v and %v", left.Type(), right.Type()) 49 | } 50 | } 51 | 52 | func (l Less) String() string { 53 | return fmt.Sprintf("(%v < %v)", l.l, l.r) 54 | } 55 | 56 | func (l Less) Compile(c *compiler.Compiler) (position int, err error) { 57 | if l.IsConstExpression() { 58 | o, err := l.Eval() 59 | if err != nil { 60 | return 0, c.NewError(l.pos, err.Error()) 61 | } 62 | position = c.Emit(code.OpConstant, c.AddConstant(o)) 63 | c.Bookmark(l.pos) 64 | return position, err 65 | } 66 | 67 | // the order of the compilation of the operands is inverted because we reuse 68 | // the code.OpGreaterThan OpCode. 69 | if position, err = l.r.Compile(c); err != nil { 70 | return 71 | } 72 | if position, err = l.l.Compile(c); err != nil { 73 | return 74 | } 75 | position = c.Emit(code.OpGreaterThan) 76 | c.Bookmark(l.pos) 77 | return 78 | } 79 | 80 | func (l Less) IsConstExpression() bool { 81 | return l.l.IsConstExpression() && l.r.IsConstExpression() 82 | } 83 | -------------------------------------------------------------------------------- /internal/ast/lesseq.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/NicoNex/tau/internal/code" 7 | "github.com/NicoNex/tau/internal/compiler" 8 | "github.com/NicoNex/tau/internal/obj" 9 | ) 10 | 11 | type LessEq struct { 12 | l Node 13 | r Node 14 | pos int 15 | } 16 | 17 | func NewLessEq(l, r Node, pos int) Node { 18 | return LessEq{ 19 | l: l, 20 | r: r, 21 | pos: pos, 22 | } 23 | } 24 | 25 | func (l LessEq) Eval() (obj.Object, error) { 26 | left, err := l.l.Eval() 27 | if err != nil { 28 | return obj.NullObj, err 29 | } 30 | 31 | right, err := l.r.Eval() 32 | if err != nil { 33 | return obj.NullObj, err 34 | } 35 | 36 | switch { 37 | case obj.AssertTypes(left, obj.IntType) && obj.AssertTypes(right, obj.IntType): 38 | return obj.ParseBool(left.Int() <= right.Int()), nil 39 | 40 | case obj.AssertTypes(left, obj.IntType, obj.FloatType) && obj.AssertTypes(right, obj.IntType, obj.FloatType): 41 | l, r := obj.ToFloat(left, right) 42 | return obj.ParseBool(l <= r), nil 43 | 44 | case obj.AssertTypes(left, obj.StringType) && obj.AssertTypes(right, obj.StringType): 45 | return obj.ParseBool(left.String() <= right.String()), nil 46 | 47 | default: 48 | return obj.NullObj, fmt.Errorf("unsupported operator '<=' for types %v and %v", left.Type(), right.Type()) 49 | } 50 | } 51 | 52 | func (l LessEq) String() string { 53 | return fmt.Sprintf("(%v <= %v)", l.l, l.r) 54 | } 55 | 56 | func (l LessEq) Compile(c *compiler.Compiler) (position int, err error) { 57 | if l.IsConstExpression() { 58 | o, err := l.Eval() 59 | if err != nil { 60 | return 0, c.NewError(l.pos, err.Error()) 61 | } 62 | position = c.Emit(code.OpConstant, c.AddConstant(o)) 63 | c.Bookmark(l.pos) 64 | return position, err 65 | } 66 | 67 | if position, err = l.r.Compile(c); err != nil { 68 | return 69 | } 70 | if position, err = l.l.Compile(c); err != nil { 71 | return 72 | } 73 | position = c.Emit(code.OpGreaterThanEqual) 74 | c.Bookmark(l.pos) 75 | return 76 | } 77 | 78 | func (l LessEq) IsConstExpression() bool { 79 | return l.l.IsConstExpression() && l.r.IsConstExpression() 80 | } 81 | -------------------------------------------------------------------------------- /internal/ast/list.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "strings" 7 | 8 | "github.com/NicoNex/tau/internal/code" 9 | "github.com/NicoNex/tau/internal/compiler" 10 | "github.com/NicoNex/tau/internal/obj" 11 | ) 12 | 13 | type List []Node 14 | 15 | func NewList(elements ...Node) Node { 16 | var ret List 17 | 18 | for _, e := range elements { 19 | ret = append(ret, e) 20 | } 21 | return ret 22 | } 23 | 24 | // TODO: optimise this for the case where all the list elements are constant expressions. 25 | func (l List) Eval() (obj.Object, error) { 26 | return obj.NullObj, errors.New("ast.Index: not a constant expression") 27 | } 28 | 29 | func (l List) String() string { 30 | var elements []string 31 | 32 | for _, e := range l { 33 | if s, ok := e.(String); ok { 34 | elements = append(elements, s.Quoted()) 35 | } else { 36 | elements = append(elements, e.String()) 37 | } 38 | } 39 | return fmt.Sprintf("[%s]", strings.Join(elements, ", ")) 40 | } 41 | 42 | func (l List) Compile(c *compiler.Compiler) (position int, err error) { 43 | for _, n := range l { 44 | if position, err = n.Compile(c); err != nil { 45 | return 46 | } 47 | } 48 | position = c.Emit(code.OpList, len(l)) 49 | return 50 | } 51 | 52 | func (l List) IsConstExpression() bool { 53 | return false 54 | } 55 | -------------------------------------------------------------------------------- /internal/ast/map.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "sort" 7 | "strings" 8 | 9 | "github.com/NicoNex/tau/internal/code" 10 | "github.com/NicoNex/tau/internal/compiler" 11 | "github.com/NicoNex/tau/internal/obj" 12 | ) 13 | 14 | type Map struct { 15 | m [][2]Node 16 | pos int 17 | } 18 | 19 | func NewMap(pos int, pairs ...[2]Node) Node { 20 | return Map{ 21 | m: pairs, 22 | pos: pos, 23 | } 24 | } 25 | 26 | func (m Map) Eval() (obj.Object, error) { 27 | return obj.NullObj, errors.New("ast.Index: not a constant expression") 28 | } 29 | 30 | func (m Map) String() string { 31 | var ( 32 | buf strings.Builder 33 | i = 1 34 | ) 35 | 36 | buf.WriteString("{") 37 | for _, pair := range m.m { 38 | var ( 39 | k = pair[0] 40 | v = pair[1] 41 | key string 42 | val string 43 | ) 44 | 45 | if s, ok := k.(String); ok { 46 | key = s.Quoted() 47 | } else { 48 | key = k.String() 49 | } 50 | 51 | if s, ok := v.(String); ok { 52 | val = s.Quoted() 53 | } else { 54 | val = v.String() 55 | } 56 | 57 | buf.WriteString(fmt.Sprintf("%s: %s", key, val)) 58 | 59 | if i < len(m.m) { 60 | buf.WriteString(", ") 61 | } 62 | i += 1 63 | } 64 | buf.WriteString("}") 65 | return buf.String() 66 | } 67 | 68 | func (m Map) Compile(c *compiler.Compiler) (position int, err error) { 69 | sort.Slice(m.m, func(i, j int) bool { 70 | return m.m[i][0].String() < m.m[j][0].String() 71 | }) 72 | 73 | for _, pair := range m.m { 74 | if position, err = pair[0].Compile(c); err != nil { 75 | return 76 | } 77 | 78 | if position, err = pair[1].Compile(c); err != nil { 79 | return 80 | } 81 | } 82 | 83 | position = c.Emit(code.OpMap, len(m.m)*2) 84 | c.Bookmark(m.pos) 85 | return 86 | } 87 | 88 | func (m Map) IsConstExpression() bool { 89 | return false 90 | } 91 | -------------------------------------------------------------------------------- /internal/ast/minus.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/NicoNex/tau/internal/code" 7 | "github.com/NicoNex/tau/internal/compiler" 8 | "github.com/NicoNex/tau/internal/obj" 9 | ) 10 | 11 | type Minus struct { 12 | l Node 13 | r Node 14 | pos int 15 | } 16 | 17 | func NewMinus(l, r Node, pos int) Node { 18 | return Minus{ 19 | l: l, 20 | r: r, 21 | pos: pos, 22 | } 23 | } 24 | 25 | func (m Minus) Eval() (obj.Object, error) { 26 | left, err := m.l.Eval() 27 | if err != nil { 28 | return obj.NullObj, err 29 | } 30 | 31 | right, err := m.r.Eval() 32 | if err != nil { 33 | return obj.NullObj, err 34 | } 35 | 36 | if !obj.AssertTypes(left, obj.IntType, obj.FloatType) { 37 | return obj.NullObj, fmt.Errorf("unsupported operator '-' for type %v", left.Type()) 38 | } 39 | if !obj.AssertTypes(right, obj.IntType, obj.FloatType) { 40 | return obj.NullObj, fmt.Errorf("unsupported operator '-' for type %v", right.Type()) 41 | } 42 | 43 | if obj.AssertTypes(left, obj.IntType) && obj.AssertTypes(right, obj.IntType) { 44 | return obj.NewInteger(left.Int() - right.Int()), nil 45 | } 46 | 47 | l, r := obj.ToFloat(left, right) 48 | return obj.NewFloat(l - r), nil 49 | } 50 | 51 | func (m Minus) String() string { 52 | return fmt.Sprintf("(%v - %v)", m.l, m.r) 53 | } 54 | 55 | func (m Minus) Compile(c *compiler.Compiler) (position int, err error) { 56 | if m.IsConstExpression() { 57 | o, err := m.Eval() 58 | if err != nil { 59 | return 0, c.NewError(m.pos, err.Error()) 60 | } 61 | position = c.Emit(code.OpConstant, c.AddConstant(o)) 62 | c.Bookmark(m.pos) 63 | return position, err 64 | } 65 | 66 | if position, err = m.l.Compile(c); err != nil { 67 | return 68 | } 69 | if position, err = m.r.Compile(c); err != nil { 70 | return 71 | } 72 | position = c.Emit(code.OpSub) 73 | c.Bookmark(m.pos) 74 | return 75 | } 76 | 77 | func (m Minus) IsConstExpression() bool { 78 | return m.l.IsConstExpression() && m.r.IsConstExpression() 79 | } 80 | -------------------------------------------------------------------------------- /internal/ast/minusassign.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "github.com/NicoNex/tau/internal/compiler" 8 | "github.com/NicoNex/tau/internal/obj" 9 | ) 10 | 11 | type MinusAssign struct { 12 | l Node 13 | r Node 14 | pos int 15 | } 16 | 17 | func NewMinusAssign(l, r Node, pos int) Node { 18 | return MinusAssign{ 19 | l: l, 20 | r: r, 21 | pos: pos, 22 | } 23 | } 24 | 25 | func (m MinusAssign) Eval() (obj.Object, error) { 26 | return obj.NullObj, errors.New("ast.MinusAssign: not a constant expression") 27 | } 28 | 29 | func (m MinusAssign) String() string { 30 | return fmt.Sprintf("(%v -= %v)", m.l, m.r) 31 | } 32 | 33 | func (m MinusAssign) Compile(c *compiler.Compiler) (position int, err error) { 34 | n := Assign{l: m.l, r: Minus{l: m.l, r: m.r, pos: m.pos}, pos: m.pos} 35 | position, err = n.Compile(c) 36 | c.Bookmark(n.pos) 37 | return 38 | } 39 | 40 | func (m MinusAssign) IsConstExpression() bool { 41 | return false 42 | } 43 | -------------------------------------------------------------------------------- /internal/ast/minusminus.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "github.com/NicoNex/tau/internal/compiler" 8 | "github.com/NicoNex/tau/internal/obj" 9 | ) 10 | 11 | type MinusMinus struct { 12 | r Node 13 | pos int 14 | } 15 | 16 | func NewMinusMinus(r Node, pos int) Node { 17 | return MinusMinus{ 18 | r: r, 19 | pos: pos, 20 | } 21 | } 22 | 23 | func (m MinusMinus) Eval() (obj.Object, error) { 24 | return obj.NullObj, errors.New("ast.MinusMinus: not a constant expression") 25 | } 26 | 27 | func (m MinusMinus) String() string { 28 | return fmt.Sprintf("--%v", m.r) 29 | } 30 | 31 | func (m MinusMinus) Compile(c *compiler.Compiler) (position int, err error) { 32 | n := Assign{l: m.r, r: Minus{l: m.r, r: Integer(1), pos: m.pos}, pos: m.pos} 33 | position, err = n.Compile(c) 34 | c.Bookmark(n.pos) 35 | return 36 | } 37 | 38 | func (m MinusMinus) IsConstExpression() bool { 39 | return false 40 | } 41 | -------------------------------------------------------------------------------- /internal/ast/mod.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/NicoNex/tau/internal/code" 7 | "github.com/NicoNex/tau/internal/compiler" 8 | "github.com/NicoNex/tau/internal/obj" 9 | ) 10 | 11 | type Mod struct { 12 | l Node 13 | r Node 14 | pos int 15 | } 16 | 17 | func NewMod(l, r Node, pos int) Node { 18 | return Mod{ 19 | l: l, 20 | r: r, 21 | pos: pos, 22 | } 23 | } 24 | 25 | func (m Mod) Eval() (obj.Object, error) { 26 | left, err := m.l.Eval() 27 | if err != nil { 28 | return obj.NullObj, err 29 | } 30 | 31 | right, err := m.r.Eval() 32 | if err != nil { 33 | return obj.NullObj, err 34 | } 35 | 36 | if !obj.AssertTypes(left, obj.IntType) { 37 | return obj.NullObj, fmt.Errorf("unsupported operator '%%' for type %v", left.Type()) 38 | } 39 | if !obj.AssertTypes(right, obj.IntType) { 40 | return obj.NullObj, fmt.Errorf("unsupported operator '%%' for type %v", right.Type()) 41 | } 42 | if right.Int() == 0 { 43 | return obj.NullObj, fmt.Errorf("can't divide by 0") 44 | } 45 | 46 | return obj.NewInteger(left.Int() % right.Int()), nil 47 | } 48 | 49 | func (m Mod) String() string { 50 | return fmt.Sprintf("(%v %% %v)", m.l, m.r) 51 | } 52 | 53 | func (m Mod) Compile(c *compiler.Compiler) (position int, err error) { 54 | if m.IsConstExpression() { 55 | o, err := m.Eval() 56 | if err != nil { 57 | return 0, c.NewError(m.pos, err.Error()) 58 | } 59 | position = c.Emit(code.OpConstant, c.AddConstant(o)) 60 | c.Bookmark(m.pos) 61 | return position, err 62 | } 63 | 64 | if position, err = m.l.Compile(c); err != nil { 65 | return 66 | } 67 | if position, err = m.r.Compile(c); err != nil { 68 | return 69 | } 70 | position = c.Emit(code.OpMod) 71 | c.Bookmark(m.pos) 72 | return 73 | } 74 | 75 | func (m Mod) IsConstExpression() bool { 76 | return m.l.IsConstExpression() && m.r.IsConstExpression() 77 | } 78 | -------------------------------------------------------------------------------- /internal/ast/modassign.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "github.com/NicoNex/tau/internal/compiler" 8 | "github.com/NicoNex/tau/internal/obj" 9 | ) 10 | 11 | type ModAssign struct { 12 | l Node 13 | r Node 14 | pos int 15 | } 16 | 17 | func NewModAssign(l, r Node, pos int) Node { 18 | return ModAssign{ 19 | l: l, 20 | r: r, 21 | pos: pos, 22 | } 23 | } 24 | 25 | func (m ModAssign) Eval() (obj.Object, error) { 26 | return obj.NullObj, errors.New("ast.ModAssign: not a constant expression") 27 | } 28 | 29 | func (m ModAssign) String() string { 30 | return fmt.Sprintf("(%v %%= %v)", m.l, m.r) 31 | } 32 | 33 | func (m ModAssign) Compile(c *compiler.Compiler) (position int, err error) { 34 | n := Assign{l: m.l, r: Mod{l: m.l, r: m.r, pos: m.pos}, pos: m.pos} 35 | position, err = n.Compile(c) 36 | c.Bookmark(n.pos) 37 | return 38 | } 39 | 40 | func (m ModAssign) IsConstExpression() bool { 41 | return false 42 | } 43 | -------------------------------------------------------------------------------- /internal/ast/node.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "github.com/NicoNex/tau/internal/compiler" 5 | "github.com/NicoNex/tau/internal/obj" 6 | ) 7 | 8 | type parseFn func(string, string) (Node, []error) 9 | 10 | type Node interface { 11 | Eval() (obj.Object, error) 12 | String() string 13 | compiler.Compilable 14 | } 15 | 16 | // Checks whether o is of type obj.ErrorType. 17 | func isError(o obj.Object) bool { 18 | return o.Type() == obj.ErrorType 19 | } 20 | -------------------------------------------------------------------------------- /internal/ast/notequals.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/NicoNex/tau/internal/code" 7 | "github.com/NicoNex/tau/internal/compiler" 8 | "github.com/NicoNex/tau/internal/obj" 9 | ) 10 | 11 | type NotEquals struct { 12 | l Node 13 | r Node 14 | pos int 15 | } 16 | 17 | func NewNotEquals(l, r Node, pos int) Node { 18 | return NotEquals{ 19 | l: l, 20 | r: r, 21 | pos: pos, 22 | } 23 | } 24 | 25 | func (n NotEquals) Eval() (obj.Object, error) { 26 | left, err := n.l.Eval() 27 | if err != nil { 28 | return obj.NullObj, err 29 | } 30 | 31 | right, err := n.r.Eval() 32 | if err != nil { 33 | return obj.NullObj, err 34 | } 35 | 36 | if !obj.AssertTypes(left, obj.IntType, obj.FloatType, obj.StringType, obj.BoolType, obj.NullType) { 37 | return obj.NullObj, fmt.Errorf("unsupported operator '!=' for type %v", left.Type()) 38 | } 39 | if !obj.AssertTypes(right, obj.IntType, obj.FloatType, obj.StringType, obj.BoolType, obj.NullType) { 40 | return obj.NullObj, fmt.Errorf("unsupported operator '!=' for type %v", right.Type()) 41 | } 42 | 43 | switch { 44 | case obj.AssertTypes(right, obj.BoolType, obj.NullType) || obj.AssertTypes(right, obj.BoolType, obj.NullType): 45 | return obj.ParseBool(left.Type() != right.Type() || left.Int() != right.Int()), nil 46 | 47 | case obj.AssertTypes(left, obj.StringType) && obj.AssertTypes(right, obj.StringType): 48 | return obj.ParseBool(left.String() != right.String()), nil 49 | 50 | case obj.AssertTypes(left, obj.IntType) && obj.AssertTypes(right, obj.IntType): 51 | return obj.ParseBool(left.Int() != right.Int()), nil 52 | 53 | case obj.AssertTypes(left, obj.FloatType, obj.IntType) && obj.AssertTypes(right, obj.FloatType, obj.IntType): 54 | l, r := obj.ToFloat(left, right) 55 | return obj.ParseBool(l != r), nil 56 | default: 57 | return obj.TrueObj, nil 58 | } 59 | } 60 | 61 | func (n NotEquals) String() string { 62 | return fmt.Sprintf("(%v != %v)", n.l, n.r) 63 | } 64 | 65 | func (n NotEquals) Compile(c *compiler.Compiler) (position int, err error) { 66 | if n.IsConstExpression() { 67 | o, err := n.Eval() 68 | if err != nil { 69 | return 0, c.NewError(n.pos, err.Error()) 70 | } 71 | position = c.Emit(code.OpConstant, c.AddConstant(o)) 72 | c.Bookmark(n.pos) 73 | return position, err 74 | } 75 | 76 | if position, err = n.l.Compile(c); err != nil { 77 | return 78 | } 79 | if position, err = n.r.Compile(c); err != nil { 80 | return 81 | } 82 | position = c.Emit(code.OpNotEqual) 83 | c.Bookmark(n.pos) 84 | return 85 | } 86 | 87 | func (n NotEquals) IsConstExpression() bool { 88 | return n.l.IsConstExpression() && n.r.IsConstExpression() 89 | } 90 | -------------------------------------------------------------------------------- /internal/ast/null.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "github.com/NicoNex/tau/internal/code" 5 | "github.com/NicoNex/tau/internal/compiler" 6 | "github.com/NicoNex/tau/internal/obj" 7 | ) 8 | 9 | type Null struct{} 10 | 11 | func NewNull() Null { 12 | return Null{} 13 | } 14 | 15 | func (n Null) Eval() (obj.Object, error) { 16 | return obj.NullObj, nil 17 | } 18 | 19 | func (n Null) String() string { 20 | return "null" 21 | } 22 | 23 | func (n Null) Compile(c *compiler.Compiler) (position int, err error) { 24 | return c.Emit(code.OpConstant, c.AddConstant(obj.NullObj)), nil 25 | } 26 | 27 | func (n Null) IsConstExpression() bool { 28 | return true 29 | } 30 | -------------------------------------------------------------------------------- /internal/ast/or.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/NicoNex/tau/internal/code" 7 | "github.com/NicoNex/tau/internal/compiler" 8 | "github.com/NicoNex/tau/internal/obj" 9 | ) 10 | 11 | type Or struct { 12 | l Node 13 | r Node 14 | pos int 15 | } 16 | 17 | func NewOr(l, r Node, pos int) Node { 18 | return Or{ 19 | l: l, 20 | r: r, 21 | pos: pos, 22 | } 23 | } 24 | 25 | func (o Or) Eval() (obj.Object, error) { 26 | left, err := o.l.Eval() 27 | if err != nil { 28 | return obj.NullObj, err 29 | } 30 | 31 | right, err := o.r.Eval() 32 | if err != nil { 33 | return obj.NullObj, err 34 | } 35 | 36 | return obj.ParseBool(left.IsTruthy() || right.IsTruthy()), nil 37 | } 38 | 39 | func (o Or) String() string { 40 | return fmt.Sprintf("(%v || %v)", o.l, o.r) 41 | } 42 | 43 | func (o Or) Compile(c *compiler.Compiler) (position int, err error) { 44 | if o.IsConstExpression() { 45 | object, err := o.Eval() 46 | if err != nil { 47 | return 0, c.NewError(o.pos, err.Error()) 48 | } 49 | position = c.Emit(code.OpConstant, c.AddConstant(object)) 50 | c.Bookmark(o.pos) 51 | return position, err 52 | } 53 | 54 | if position, err = o.l.Compile(c); err != nil { 55 | return 56 | } 57 | 58 | position = c.Emit(code.OpBang) 59 | jntPos := c.Emit(code.OpJumpNotTruthy, compiler.GenericPlaceholder) 60 | // Emit OpFalse because the value will be popped from the stack. 61 | position = c.Emit(code.OpFalse) 62 | 63 | if position, err = o.r.Compile(c); err != nil { 64 | return 65 | } 66 | position = c.Emit(code.OpOr) 67 | jmpPos := c.Emit(code.OpJump, compiler.GenericPlaceholder) 68 | // Emit OpTrue because the expression needs to return false if jumped here. 69 | position = c.Emit(code.OpTrue) 70 | c.ReplaceOperand(jntPos, position) 71 | c.ReplaceOperand(jmpPos, c.Pos()) 72 | c.Bookmark(o.pos) 73 | 74 | return 75 | } 76 | 77 | func (o Or) IsConstExpression() bool { 78 | return o.l.IsConstExpression() && o.r.IsConstExpression() 79 | } 80 | -------------------------------------------------------------------------------- /internal/ast/plus.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/NicoNex/tau/internal/code" 7 | "github.com/NicoNex/tau/internal/compiler" 8 | "github.com/NicoNex/tau/internal/obj" 9 | ) 10 | 11 | type Plus struct { 12 | l Node 13 | r Node 14 | pos int 15 | } 16 | 17 | func NewPlus(l, r Node, pos int) Node { 18 | return Plus{ 19 | l: l, 20 | r: r, 21 | pos: pos, 22 | } 23 | } 24 | 25 | func (p Plus) Eval() (obj.Object, error) { 26 | left, err := p.l.Eval() 27 | if err != nil { 28 | return obj.NullObj, err 29 | } 30 | 31 | right, err := p.r.Eval() 32 | if err != nil { 33 | return obj.NullObj, err 34 | } 35 | 36 | if !obj.AssertTypes(left, obj.IntType, obj.FloatType, obj.StringType) { 37 | return obj.NullObj, fmt.Errorf("unsupported operator '+' for type %v", left.Type()) 38 | } 39 | if !obj.AssertTypes(right, obj.IntType, obj.FloatType, obj.StringType) { 40 | return obj.NullObj, fmt.Errorf("unsupported operator '+' for type %v", right.Type()) 41 | } 42 | 43 | switch { 44 | case obj.AssertTypes(left, obj.StringType) && obj.AssertTypes(right, obj.StringType): 45 | return obj.NewString(left.String() + right.String()), nil 46 | 47 | case obj.AssertTypes(left, obj.IntType) && obj.AssertTypes(right, obj.IntType): 48 | return obj.NewInteger(left.Int() + right.Int()), nil 49 | 50 | case obj.AssertTypes(left, obj.FloatType, obj.IntType) && obj.AssertTypes(right, obj.FloatType, obj.IntType): 51 | l, r := obj.ToFloat(left, right) 52 | return obj.NewFloat(l + r), nil 53 | 54 | default: 55 | return obj.NullObj, fmt.Errorf( 56 | "invalid operation %v + %v (wrong types %v and %v)", 57 | left, right, left.Type(), right.Type(), 58 | ) 59 | } 60 | } 61 | 62 | func (p Plus) String() string { 63 | return fmt.Sprintf("(%v + %v)", p.l, p.r) 64 | } 65 | 66 | func (p Plus) Compile(c *compiler.Compiler) (position int, err error) { 67 | if p.IsConstExpression() { 68 | o, err := p.Eval() 69 | if err != nil { 70 | return 0, c.NewError(p.pos, err.Error()) 71 | } 72 | position = c.Emit(code.OpConstant, c.AddConstant(o)) 73 | c.Bookmark(p.pos) 74 | return position, err 75 | } 76 | 77 | if position, err = p.l.Compile(c); err != nil { 78 | return 79 | } 80 | if position, err = p.r.Compile(c); err != nil { 81 | return 82 | } 83 | position = c.Emit(code.OpAdd) 84 | c.Bookmark(p.pos) 85 | return 86 | } 87 | 88 | func (p Plus) IsConstExpression() bool { 89 | return p.l.IsConstExpression() && p.r.IsConstExpression() 90 | } 91 | -------------------------------------------------------------------------------- /internal/ast/plusassign.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "github.com/NicoNex/tau/internal/compiler" 8 | "github.com/NicoNex/tau/internal/obj" 9 | ) 10 | 11 | type PlusAssign struct { 12 | l Node 13 | r Node 14 | pos int 15 | } 16 | 17 | func NewPlusAssign(l, r Node, pos int) Node { 18 | return PlusAssign{ 19 | l: l, 20 | r: r, 21 | pos: pos, 22 | } 23 | } 24 | 25 | func (p PlusAssign) Eval() (obj.Object, error) { 26 | return obj.NullObj, errors.New("ast.PlusAssign: not a constant expression") 27 | } 28 | 29 | func (p PlusAssign) String() string { 30 | return fmt.Sprintf("(%v += %v)", p.l, p.r) 31 | } 32 | 33 | func (p PlusAssign) Compile(c *compiler.Compiler) (position int, err error) { 34 | n := Assign{l: p.l, r: Plus{l: p.l, r: p.r, pos: p.pos}, pos: p.pos} 35 | position, err = n.Compile(c) 36 | c.Bookmark(n.pos) 37 | return 38 | } 39 | 40 | func (p PlusAssign) IsConstExpression() bool { 41 | return false 42 | } 43 | -------------------------------------------------------------------------------- /internal/ast/plusplus.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | func NewPlusPlus(r Node, pos int) Node { 4 | return Assign{l: r, r: Plus{l: r, r: Integer(1), pos: pos}, pos: pos} 5 | } 6 | -------------------------------------------------------------------------------- /internal/ast/prefixmin.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/NicoNex/tau/internal/code" 7 | "github.com/NicoNex/tau/internal/compiler" 8 | "github.com/NicoNex/tau/internal/obj" 9 | ) 10 | 11 | type PrefixMinus struct { 12 | n Node 13 | pos int 14 | } 15 | 16 | func NewPrefixMinus(n Node, pos int) Node { 17 | return PrefixMinus{ 18 | n: n, 19 | pos: pos, 20 | } 21 | } 22 | 23 | func (p PrefixMinus) Eval() (obj.Object, error) { 24 | right, err := p.n.Eval() 25 | if err != nil { 26 | return obj.NullObj, err 27 | } 28 | 29 | switch right.Type() { 30 | case obj.IntType: 31 | return obj.NewInteger(-right.Int()), nil 32 | case obj.FloatType: 33 | return obj.NewFloat(-right.Float()), nil 34 | default: 35 | return obj.NullObj, fmt.Errorf("unsupported prefix operator '-' for type %v", right.Type()) 36 | } 37 | } 38 | 39 | func (p PrefixMinus) String() string { 40 | return fmt.Sprintf("-%v", p.n) 41 | } 42 | 43 | func (p PrefixMinus) Compile(c *compiler.Compiler) (position int, err error) { 44 | if p.IsConstExpression() { 45 | o, err := p.Eval() 46 | if err != nil { 47 | return 0, c.NewError(p.pos, err.Error()) 48 | } 49 | position = c.Emit(code.OpConstant, c.AddConstant(o)) 50 | c.Bookmark(p.pos) 51 | return position, err 52 | } 53 | 54 | if position, err = p.n.Compile(c); err != nil { 55 | return 56 | } 57 | position = c.Emit(code.OpMinus) 58 | c.Bookmark(p.pos) 59 | return 60 | } 61 | 62 | func (p PrefixMinus) IsConstExpression() bool { 63 | return p.n.IsConstExpression() 64 | } 65 | -------------------------------------------------------------------------------- /internal/ast/rawstring.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/NicoNex/tau/internal/code" 7 | "github.com/NicoNex/tau/internal/compiler" 8 | "github.com/NicoNex/tau/internal/obj" 9 | ) 10 | 11 | type RawString string 12 | 13 | func NewRawString(s string) Node { 14 | return RawString(s) 15 | } 16 | 17 | func (r RawString) Eval() (obj.Object, error) { 18 | return obj.NewString(string(r)), nil 19 | } 20 | 21 | func (r RawString) String() string { 22 | return string(r) 23 | } 24 | 25 | func (r RawString) Quoted() string { 26 | var buf strings.Builder 27 | 28 | buf.WriteRune('`') 29 | buf.WriteString(string(r)) 30 | buf.WriteRune('`') 31 | return buf.String() 32 | } 33 | 34 | func (r RawString) Compile(c *compiler.Compiler) (position int, err error) { 35 | return c.Emit(code.OpConstant, c.AddConstant(obj.NewString(string(r)))), nil 36 | } 37 | 38 | func (r RawString) IsConstExpression() bool { 39 | return true 40 | } 41 | -------------------------------------------------------------------------------- /internal/ast/return.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "github.com/NicoNex/tau/internal/code" 8 | "github.com/NicoNex/tau/internal/compiler" 9 | "github.com/NicoNex/tau/internal/obj" 10 | ) 11 | 12 | type Return struct { 13 | v Node 14 | pos int 15 | } 16 | 17 | func NewReturn(n Node, pos int) Node { 18 | return Return{ 19 | v: n, 20 | pos: pos, 21 | } 22 | } 23 | 24 | func (r Return) Eval() (obj.Object, error) { 25 | return obj.NullObj, errors.New("ast.Return: not a constant expression") 26 | } 27 | 28 | func (r Return) String() string { 29 | return fmt.Sprintf("return %v", r.v) 30 | } 31 | 32 | func (r Return) Compile(c *compiler.Compiler) (position int, err error) { 33 | if position, err = r.v.Compile(c); err != nil { 34 | return 35 | } 36 | position = c.Emit(code.OpReturnValue) 37 | c.Bookmark(r.pos) 38 | return 39 | } 40 | 41 | func (r Return) IsConstExpression() bool { 42 | return false 43 | } 44 | -------------------------------------------------------------------------------- /internal/ast/string.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "strconv" 7 | "strings" 8 | "unicode/utf8" 9 | 10 | "github.com/NicoNex/tau/internal/code" 11 | "github.com/NicoNex/tau/internal/compiler" 12 | "github.com/NicoNex/tau/internal/obj" 13 | ) 14 | 15 | type String struct { 16 | s string 17 | parse parseFn 18 | substr []Node 19 | pos int 20 | } 21 | 22 | func NewString(file, s string, parse parseFn, pos int) (Node, error) { 23 | str, err := escape(s) 24 | if err != nil { 25 | return nil, err 26 | } 27 | 28 | i := newInterpolator(file, str, parse) 29 | nodes, str, err := i.nodes() 30 | 31 | if len(nodes) == 0 { 32 | return NewRawString(str), nil 33 | } 34 | return String{s: str, parse: parse, substr: nodes, pos: pos}, err 35 | } 36 | 37 | func (s String) Eval() (obj.Object, error) { 38 | return obj.NullObj, errors.New("ast.String: not a constant expression") 39 | } 40 | 41 | func (s String) String() string { 42 | return s.s 43 | } 44 | 45 | func (s String) Quoted() string { 46 | return strconv.Quote(s.s) 47 | } 48 | 49 | func (s String) Compile(c *compiler.Compiler) (position int, err error) { 50 | if len(s.substr) == 0 { 51 | position = c.Emit(code.OpConstant, c.AddConstant(obj.NewString(s.s))) 52 | c.Bookmark(s.pos) 53 | return 54 | } 55 | 56 | for _, sub := range s.substr { 57 | if position, err = sub.Compile(c); err != nil { 58 | return 59 | } 60 | c.RemoveLast() 61 | } 62 | 63 | position = c.Emit(code.OpInterpolate, c.AddConstant(obj.NewString(s.s)), len(s.substr)) 64 | c.Bookmark(s.pos) 65 | return 66 | } 67 | 68 | func (s String) IsConstExpression() bool { 69 | return len(s.substr) == 0 70 | } 71 | 72 | func escape(s string) (string, error) { 73 | var buf strings.Builder 74 | 75 | for i := 0; i < len(s); { 76 | r, width := utf8.DecodeRuneInString(s[i:]) 77 | 78 | if r == '\\' { 79 | i += width 80 | if i < len(s) { 81 | r, width := utf8.DecodeRuneInString(s[i:]) 82 | esc, err := escapeRune(r) 83 | if err != nil { 84 | return "", err 85 | } 86 | buf.WriteRune(esc) 87 | i += width 88 | continue 89 | } else { 90 | return "", errors.New("newline in string") 91 | } 92 | } else { 93 | buf.WriteRune(r) 94 | } 95 | 96 | i += width 97 | } 98 | 99 | return buf.String(), nil 100 | } 101 | 102 | func escapeRune(r rune) (rune, error) { 103 | switch r { 104 | case 'a': 105 | return '\a', nil 106 | case 'b': 107 | return '\b', nil 108 | case 'f': 109 | return '\f', nil 110 | case 'n': 111 | return '\n', nil 112 | case 'r': 113 | return '\r', nil 114 | case 't': 115 | return '\t', nil 116 | case 'v': 117 | return '\v', nil 118 | case '\\': 119 | return '\\', nil 120 | case '\'': 121 | return '\'', nil 122 | case '"': 123 | return '"', nil 124 | default: 125 | return r, fmt.Errorf(`unknown escape "\%c"`, r) 126 | } 127 | } 128 | 129 | const eof = -1 130 | 131 | var errBadInterpolationSyntax = errors.New("bad interpolation syntax") 132 | 133 | type interpolator struct { 134 | s string 135 | file string 136 | parse parseFn 137 | pos int 138 | width int 139 | nblocks int 140 | inQuotes bool 141 | inBacktick bool 142 | strings.Builder 143 | } 144 | 145 | func newInterpolator(file, s string, parse parseFn) interpolator { 146 | return interpolator{s: s, file: file, parse: parse} 147 | } 148 | 149 | func (i *interpolator) next() (r rune) { 150 | if i.pos >= len(i.s) { 151 | i.width = 0 152 | return eof 153 | } 154 | 155 | r, i.width = utf8.DecodeRuneInString(i.s[i.pos:]) 156 | i.pos += i.width 157 | return 158 | } 159 | 160 | func (i *interpolator) backup() { 161 | i.pos -= i.width 162 | } 163 | 164 | func (i *interpolator) peek() rune { 165 | r := i.next() 166 | i.backup() 167 | return r 168 | } 169 | 170 | func (i *interpolator) enterBlock() { 171 | i.nblocks++ 172 | } 173 | 174 | func (i *interpolator) exitBlock() { 175 | i.nblocks-- 176 | } 177 | 178 | func (i *interpolator) insideBlock() bool { 179 | return i.nblocks > 0 180 | } 181 | 182 | func (i *interpolator) quotes() { 183 | i.inQuotes = !i.inQuotes 184 | } 185 | 186 | func (i *interpolator) backtick() { 187 | i.inBacktick = !i.inBacktick 188 | } 189 | 190 | func (i *interpolator) insideString() bool { 191 | return i.inQuotes || i.inBacktick 192 | } 193 | 194 | func (i *interpolator) acceptUntil(start, end rune) (string, error) { 195 | var buf strings.Builder 196 | 197 | loop: 198 | for r := i.next(); ; r = i.next() { 199 | switch r { 200 | case eof: 201 | return "", errBadInterpolationSyntax 202 | 203 | case '"': 204 | i.quotes() 205 | 206 | case '`': 207 | i.backtick() 208 | 209 | case start: 210 | if !i.insideString() { 211 | i.enterBlock() 212 | } 213 | 214 | case end: 215 | if !i.insideString() { 216 | if !i.insideBlock() { 217 | break loop 218 | } 219 | i.exitBlock() 220 | } 221 | } 222 | 223 | buf.WriteRune(r) 224 | } 225 | 226 | return buf.String(), nil 227 | } 228 | 229 | func (i *interpolator) nodes() ([]Node, string, error) { 230 | var nodes []Node 231 | 232 | for r := i.next(); r != eof; r = i.next() { 233 | if r == '{' { 234 | if i.peek() == '{' { 235 | i.next() 236 | goto tail 237 | } 238 | 239 | // Get the code between braces 240 | s, err := i.acceptUntil('{', '}') 241 | if err != nil { 242 | return []Node{}, "", err 243 | } else if s == "" { 244 | continue 245 | } 246 | 247 | // Parse the code 248 | tree, errs := i.parse(i.file, s) 249 | if len(errs) > 0 { 250 | return []Node{}, "", i.parserError(errs) 251 | } 252 | 253 | nodes = append(nodes, tree) 254 | i.WriteByte(0xff) 255 | continue 256 | } else if r == '}' { 257 | if i.peek() != '}' { 258 | return []Node{}, "", errBadInterpolationSyntax 259 | } 260 | i.next() 261 | } 262 | 263 | tail: 264 | i.WriteRune(r) 265 | } 266 | 267 | return nodes, i.String(), nil 268 | } 269 | 270 | func (i *interpolator) parserError(errs []error) error { 271 | var buf strings.Builder 272 | 273 | buf.WriteString("interpolation errors:\n") 274 | for _, e := range errs { 275 | buf.WriteString(e.Error()) 276 | buf.WriteByte('\n') 277 | } 278 | 279 | return errors.New(buf.String()) 280 | } 281 | -------------------------------------------------------------------------------- /internal/ast/times.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/NicoNex/tau/internal/code" 7 | "github.com/NicoNex/tau/internal/compiler" 8 | "github.com/NicoNex/tau/internal/obj" 9 | ) 10 | 11 | type Times struct { 12 | l Node 13 | r Node 14 | pos int 15 | } 16 | 17 | func NewTimes(l, r Node, pos int) Node { 18 | return Times{ 19 | l: l, 20 | r: r, 21 | pos: pos, 22 | } 23 | } 24 | 25 | func (t Times) Eval() (obj.Object, error) { 26 | left, err := t.l.Eval() 27 | if err != nil { 28 | return obj.NullObj, err 29 | } 30 | 31 | right, err := t.r.Eval() 32 | if err != nil { 33 | return obj.NullObj, err 34 | } 35 | 36 | if !obj.AssertTypes(left, obj.IntType, obj.FloatType) { 37 | return obj.NullObj, fmt.Errorf("unsupported operator '*' for type %v", left.Type()) 38 | } 39 | if !obj.AssertTypes(right, obj.IntType, obj.FloatType) { 40 | return obj.NullObj, fmt.Errorf("unsupported operator '*' for type %v", right.Type()) 41 | } 42 | 43 | if obj.AssertTypes(left, obj.IntType) && obj.AssertTypes(right, obj.IntType) { 44 | return obj.NewInteger(left.Int() * right.Int()), nil 45 | } 46 | 47 | l, r := obj.ToFloat(left, right) 48 | return obj.NewFloat(l * r), nil 49 | } 50 | 51 | func (t Times) String() string { 52 | return fmt.Sprintf("(%v * %v)", t.l, t.r) 53 | } 54 | 55 | func (t Times) Compile(c *compiler.Compiler) (position int, err error) { 56 | if t.IsConstExpression() { 57 | o, err := t.Eval() 58 | if err != nil { 59 | return 0, c.NewError(t.pos, err.Error()) 60 | } 61 | position = c.Emit(code.OpConstant, c.AddConstant(o)) 62 | c.Bookmark(t.pos) 63 | return position, err 64 | } 65 | 66 | if position, err = t.l.Compile(c); err != nil { 67 | return 68 | } 69 | if position, err = t.r.Compile(c); err != nil { 70 | return 71 | } 72 | position = c.Emit(code.OpMul) 73 | c.Bookmark(t.pos) 74 | return 75 | } 76 | 77 | func (t Times) IsConstExpression() bool { 78 | return t.l.IsConstExpression() && t.r.IsConstExpression() 79 | } 80 | -------------------------------------------------------------------------------- /internal/ast/timesassign.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "github.com/NicoNex/tau/internal/compiler" 8 | "github.com/NicoNex/tau/internal/obj" 9 | ) 10 | 11 | type TimesAssign struct { 12 | l Node 13 | r Node 14 | pos int 15 | } 16 | 17 | func NewTimesAssign(l, r Node, pos int) Node { 18 | return TimesAssign{ 19 | l: l, 20 | r: r, 21 | pos: pos, 22 | } 23 | } 24 | 25 | func (t TimesAssign) Eval() (obj.Object, error) { 26 | return obj.NullObj, errors.New("ast.TimesAssign: not a constant expression") 27 | } 28 | 29 | func (t TimesAssign) String() string { 30 | return fmt.Sprintf("(%v *= %v)", t.l, t.r) 31 | } 32 | 33 | func (t TimesAssign) Compile(c *compiler.Compiler) (position int, err error) { 34 | n := Assign{l: t.l, r: Times{l: t.l, r: t.r, pos: t.pos}, pos: t.pos} 35 | position, err = n.Compile(c) 36 | c.Bookmark(n.pos) 37 | return 38 | } 39 | 40 | func (t TimesAssign) IsConstExpression() bool { 41 | return false 42 | } 43 | -------------------------------------------------------------------------------- /internal/code/code.go: -------------------------------------------------------------------------------- 1 | package code 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | ) 8 | 9 | type ( 10 | Instructions []byte 11 | Opcode byte 12 | ) 13 | 14 | type Definition struct { 15 | Name string 16 | OperandWidths []int 17 | } 18 | 19 | //go:generate stringer -type=Opcode 20 | const ( 21 | OpHalt Opcode = iota 22 | OpPop 23 | 24 | OpConstant 25 | OpTrue 26 | OpFalse 27 | OpNull 28 | OpList 29 | OpMap 30 | OpClosure 31 | OpCurrentClosure 32 | 33 | OpAdd 34 | OpSub 35 | OpMul 36 | OpDiv 37 | OpMod 38 | 39 | OpBwAnd 40 | OpBwOr 41 | OpBwXor 42 | OpBwNot 43 | OpBwLShift 44 | OpBwRShift 45 | 46 | OpAnd 47 | OpOr 48 | OpEqual 49 | OpNotEqual 50 | OpGreaterThan 51 | OpGreaterThanEqual 52 | 53 | OpMinus 54 | OpBang 55 | OpIndex 56 | 57 | OpCall 58 | OpConcurrentCall 59 | OpReturn 60 | OpReturnValue 61 | 62 | OpJump 63 | OpJumpNotTruthy 64 | 65 | OpDot 66 | OpDefine 67 | OpGetGlobal 68 | OpSetGlobal 69 | OpGetLocal 70 | OpSetLocal 71 | OpGetBuiltin 72 | OpGetFree 73 | OpLoadModule 74 | OpInterpolate 75 | ) 76 | 77 | var definitions = map[Opcode]*Definition{ 78 | OpHalt: {"OpHalt", []int{}}, 79 | OpPop: {"OpPop", []int{}}, 80 | 81 | OpConstant: {"OpConstant", []int{2}}, 82 | OpTrue: {"OpTrue", []int{}}, 83 | OpFalse: {"OpFalse", []int{}}, 84 | OpNull: {"OpNull", []int{}}, 85 | OpList: {"OpList", []int{2}}, 86 | OpMap: {"OpMap", []int{2}}, 87 | OpClosure: {"OpClosure", []int{2, 1}}, 88 | OpCurrentClosure: {"OpCurrentClosure", []int{}}, 89 | 90 | OpAdd: {"OpAdd", []int{}}, 91 | OpSub: {"OpSub", []int{}}, 92 | OpMul: {"OpMul", []int{}}, 93 | OpDiv: {"OpDiv", []int{}}, 94 | OpMod: {"OpMod", []int{}}, 95 | 96 | OpBwAnd: {"OpBwAnd", []int{}}, 97 | OpBwOr: {"OpBwOr", []int{}}, 98 | OpBwXor: {"OpBwXor", []int{}}, 99 | OpBwNot: {"OpBwNot", []int{}}, 100 | OpBwLShift: {"OpBwLShift", []int{}}, 101 | OpBwRShift: {"OpBwRshift", []int{}}, 102 | 103 | OpAnd: {"OpAnd", []int{}}, 104 | OpOr: {"OpOr", []int{}}, 105 | OpEqual: {"OpEqual", []int{}}, 106 | OpNotEqual: {"OpNotEqual", []int{}}, 107 | OpGreaterThan: {"OpGreaterThan", []int{}}, 108 | OpGreaterThanEqual: {"OpGreaterThanEqual", []int{}}, 109 | 110 | OpMinus: {"OpMinus", []int{}}, 111 | OpBang: {"OpBang", []int{}}, 112 | OpIndex: {"OpIndex", []int{}}, 113 | 114 | OpCall: {"OpCall", []int{1}}, 115 | OpConcurrentCall: {"OpConcurrentCall", []int{1}}, 116 | OpReturn: {"OpReturn", []int{}}, 117 | OpReturnValue: {"OpReturnValue", []int{}}, 118 | 119 | OpJump: {"OpJump", []int{2}}, 120 | OpJumpNotTruthy: {"OpJumpNotTruthy", []int{2}}, 121 | 122 | OpDot: {"OpDot", []int{}}, 123 | OpDefine: {"OpDefine", []int{}}, 124 | OpGetGlobal: {"OpGetGlobal", []int{2}}, 125 | OpSetGlobal: {"OpSetGlobal", []int{2}}, 126 | OpGetLocal: {"OpGetLocal", []int{1}}, 127 | OpSetLocal: {"OpSetLocal", []int{1}}, 128 | OpGetBuiltin: {"OpGetBuiltin", []int{1}}, 129 | OpGetFree: {"OpGetFree", []int{1}}, 130 | OpLoadModule: {"OpLoadModule", []int{}}, 131 | OpInterpolate: {"OpInterpolate", []int{2, 2}}, 132 | } 133 | 134 | func (ins Instructions) String() string { 135 | var out bytes.Buffer 136 | 137 | for i := 0; i < len(ins); { 138 | def, err := Lookup(ins[i]) 139 | if err != nil { 140 | fmt.Fprintf(&out, "Error: %s\n", err) 141 | continue 142 | } 143 | 144 | operands, read := ReadOperands(def, ins[i+1:]) 145 | fmt.Fprintf(&out, "%04d %s\n", i, ins.fmtInstruction(def, operands)) 146 | i += read + 1 147 | } 148 | 149 | return out.String() 150 | } 151 | 152 | func (ins Instructions) fmtInstruction(def *Definition, operands []int) string { 153 | var opCount = len(def.OperandWidths) 154 | 155 | if len(operands) != opCount { 156 | return fmt.Sprintf("error: operand len %d does not match defined %d\n", len(operands), opCount) 157 | } 158 | 159 | switch opCount { 160 | case 0: 161 | return def.Name 162 | 163 | case 1: 164 | return fmt.Sprintf("%s %d", def.Name, operands[0]) 165 | 166 | case 2: 167 | return fmt.Sprintf("%s %d %d", def.Name, operands[0], operands[1]) 168 | 169 | default: 170 | return fmt.Sprintf("error: unhandled operand count for %s\n", def.Name) 171 | } 172 | } 173 | 174 | func Lookup(op byte) (*Definition, error) { 175 | if d, ok := definitions[Opcode(op)]; ok { 176 | return d, nil 177 | } 178 | return nil, fmt.Errorf("opcode %d undefined", op) 179 | } 180 | 181 | // Returns the resulting bytecode from parsing the instruction. 182 | func Make(op Opcode, operands ...int) []byte { 183 | def, ok := definitions[op] 184 | if !ok { 185 | return []byte{} 186 | } 187 | 188 | instructionsLen := 1 189 | for _, w := range def.OperandWidths { 190 | instructionsLen += w 191 | } 192 | 193 | instructions := make([]byte, instructionsLen) 194 | instructions[0] = byte(op) 195 | 196 | offset := 1 197 | for i, o := range operands { 198 | width := def.OperandWidths[i] 199 | 200 | switch width { 201 | case 1: 202 | instructions[offset] = byte(o) 203 | 204 | case 2: 205 | binary.BigEndian.PutUint16(instructions[offset:], uint16(o)) 206 | 207 | case 4: 208 | binary.BigEndian.PutUint32(instructions[offset:], uint32(o)) 209 | 210 | case 8: 211 | binary.BigEndian.PutUint64(instructions[offset:], uint64(o)) 212 | } 213 | offset += width 214 | } 215 | 216 | return instructions 217 | } 218 | 219 | // Decodes the operands of a bytecode instruction. 220 | func ReadOperands(def *Definition, ins Instructions) ([]int, int) { 221 | operands := make([]int, len(def.OperandWidths)) 222 | offset := 0 223 | for i, width := range def.OperandWidths { 224 | switch width { 225 | case 1: 226 | operands[i] = int(ReadUint8(ins[offset:])) 227 | 228 | case 2: 229 | operands[i] = int(ReadUint16(ins[offset:])) 230 | 231 | case 4: 232 | operands[i] = int(ReadUint32(ins[offset:])) 233 | } 234 | offset += width 235 | } 236 | return operands, offset 237 | } 238 | 239 | func ReadUint8(ins Instructions) uint8 { 240 | return uint8(ins[0]) 241 | } 242 | 243 | func ReadUint16(ins Instructions) uint16 { 244 | return binary.BigEndian.Uint16(ins) 245 | } 246 | 247 | func ReadUint32(ins Instructions) uint32 { 248 | return binary.BigEndian.Uint32(ins) 249 | } 250 | 251 | func ReadUint64(ins Instructions) uint64 { 252 | return binary.BigEndian.Uint64(ins) 253 | } 254 | -------------------------------------------------------------------------------- /internal/code/code_test.go: -------------------------------------------------------------------------------- 1 | package code 2 | 3 | import "testing" 4 | 5 | func TestMake(t *testing.T) { 6 | tests := []struct { 7 | op Opcode 8 | operands []int 9 | expected []byte 10 | }{ 11 | {OpConstant, []int{65534}, []byte{byte(OpConstant), 255, 254}}, 12 | {OpAdd, []int{}, []byte{byte(OpAdd)}}, 13 | {OpClosure, []int{65534, 255}, []byte{byte(OpClosure), 255, 254, 255}}, 14 | } 15 | 16 | for _, tt := range tests { 17 | instructions := Make(tt.op, tt.operands...) 18 | 19 | if len(instructions) != len(tt.expected) { 20 | t.Errorf("instruction has wrong length, want=%d got=%d", len(tt.expected), len(instructions)) 21 | } 22 | 23 | for i, b := range tt.expected { 24 | if instructions[i] != tt.expected[i] { 25 | t.Errorf("wrong byte at pos %d, want=%d got=%d", i, b, instructions[i]) 26 | } 27 | } 28 | } 29 | } 30 | 31 | func TestInstructionsString(t *testing.T) { 32 | instructions := []Instructions{ 33 | Make(OpAdd), 34 | Make(OpGetLocal, 1), 35 | Make(OpConstant, 2), 36 | Make(OpConstant, 65535), 37 | Make(OpClosure, 65535, 255), 38 | } 39 | 40 | expected := `0000 OpAdd 41 | 0001 OpGetLocal 1 42 | 0003 OpConstant 2 43 | 0006 OpConstant 65535 44 | 0009 OpClosure 65535 255 45 | ` 46 | 47 | concatted := Instructions{} 48 | for _, ins := range instructions { 49 | concatted = append(concatted, ins...) 50 | } 51 | 52 | if concatted.String() != expected { 53 | t.Errorf("instructions wrongly formatted.\nwant=%q\ngot=%q", expected, concatted.String()) 54 | } 55 | } 56 | 57 | func TestReadOperands(t *testing.T) { 58 | tests := []struct { 59 | op Opcode 60 | operands []int 61 | bytesRead int 62 | }{ 63 | {OpConstant, []int{65535}, 2}, 64 | {OpGetLocal, []int{255}, 1}, 65 | {OpClosure, []int{65535, 255}, 3}, 66 | } 67 | for _, tt := range tests { 68 | instruction := Make(tt.op, tt.operands...) 69 | def, err := Lookup(byte(tt.op)) 70 | if err != nil { 71 | t.Fatalf("definition not found: %q\n", err) 72 | } 73 | operandsRead, n := ReadOperands(def, instruction[1:]) 74 | if n != tt.bytesRead { 75 | t.Fatalf("n wrong. want=%d, got=%d", tt.bytesRead, n) 76 | } 77 | for i, want := range tt.operands { 78 | if operandsRead[i] != want { 79 | t.Errorf("operand wrong. want=%d, got=%d", want, operandsRead[i]) 80 | } 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /internal/code/opcode_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -type=Opcode"; DO NOT EDIT. 2 | 3 | package code 4 | 5 | import "strconv" 6 | 7 | func _() { 8 | // An "invalid array index" compiler error signifies that the constant values have changed. 9 | // Re-run the stringer command to generate them again. 10 | var x [1]struct{} 11 | _ = x[OpHalt-0] 12 | _ = x[OpPop-1] 13 | _ = x[OpConstant-2] 14 | _ = x[OpTrue-3] 15 | _ = x[OpFalse-4] 16 | _ = x[OpNull-5] 17 | _ = x[OpList-6] 18 | _ = x[OpMap-7] 19 | _ = x[OpClosure-8] 20 | _ = x[OpCurrentClosure-9] 21 | _ = x[OpAdd-10] 22 | _ = x[OpSub-11] 23 | _ = x[OpMul-12] 24 | _ = x[OpDiv-13] 25 | _ = x[OpMod-14] 26 | _ = x[OpBwAnd-15] 27 | _ = x[OpBwOr-16] 28 | _ = x[OpBwXor-17] 29 | _ = x[OpBwNot-18] 30 | _ = x[OpBwLShift-19] 31 | _ = x[OpBwRShift-20] 32 | _ = x[OpAnd-21] 33 | _ = x[OpOr-22] 34 | _ = x[OpEqual-23] 35 | _ = x[OpNotEqual-24] 36 | _ = x[OpGreaterThan-25] 37 | _ = x[OpGreaterThanEqual-26] 38 | _ = x[OpMinus-27] 39 | _ = x[OpBang-28] 40 | _ = x[OpIndex-29] 41 | _ = x[OpCall-30] 42 | _ = x[OpConcurrentCall-31] 43 | _ = x[OpReturn-32] 44 | _ = x[OpReturnValue-33] 45 | _ = x[OpJump-34] 46 | _ = x[OpJumpNotTruthy-35] 47 | _ = x[OpDot-36] 48 | _ = x[OpDefine-37] 49 | _ = x[OpGetGlobal-38] 50 | _ = x[OpSetGlobal-39] 51 | _ = x[OpGetLocal-40] 52 | _ = x[OpSetLocal-41] 53 | _ = x[OpGetBuiltin-42] 54 | _ = x[OpGetFree-43] 55 | _ = x[OpLoadModule-44] 56 | _ = x[OpInterpolate-45] 57 | } 58 | 59 | const _Opcode_name = "OpHaltOpPopOpConstantOpTrueOpFalseOpNullOpListOpMapOpClosureOpCurrentClosureOpAddOpSubOpMulOpDivOpModOpBwAndOpBwOrOpBwXorOpBwNotOpBwLShiftOpBwRShiftOpAndOpOrOpEqualOpNotEqualOpGreaterThanOpGreaterThanEqualOpMinusOpBangOpIndexOpCallOpConcurrentCallOpReturnOpReturnValueOpJumpOpJumpNotTruthyOpDotOpDefineOpGetGlobalOpSetGlobalOpGetLocalOpSetLocalOpGetBuiltinOpGetFreeOpLoadModuleOpInterpolate" 60 | 61 | var _Opcode_index = [...]uint16{0, 6, 11, 21, 27, 34, 40, 46, 51, 60, 76, 81, 86, 91, 96, 101, 108, 114, 121, 128, 138, 148, 153, 157, 164, 174, 187, 205, 212, 218, 225, 231, 247, 255, 268, 274, 289, 294, 302, 313, 324, 334, 344, 356, 365, 377, 390} 62 | 63 | func (i Opcode) String() string { 64 | if i >= Opcode(len(_Opcode_index)-1) { 65 | return "Opcode(" + strconv.FormatInt(int64(i), 10) + ")" 66 | } 67 | return _Opcode_name[_Opcode_index[i]:_Opcode_index[i+1]] 68 | } 69 | -------------------------------------------------------------------------------- /internal/compiler/bytecode.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | struct bytecode { 6 | uint8_t *insts; 7 | struct object *consts; 8 | uint32_t len; 9 | uint32_t nconsts; 10 | uint32_t bklen; 11 | struct bookmark *bookmarks; 12 | uint32_t ndefs; 13 | }; 14 | 15 | struct buffer { 16 | uint8_t *buf; 17 | uint32_t len; 18 | uint32_t cap; 19 | }; 20 | 21 | void free_buffer(struct buffer buf); 22 | struct buffer tau_encode(struct bytecode bc); 23 | struct bytecode tau_decode(uint8_t *bytes, size_t len); 24 | -------------------------------------------------------------------------------- /internal/compiler/codec.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "bytecode.h" 7 | #include "../obj/object.h" 8 | #include "../tauerr/bookmark.h" 9 | 10 | __attribute__((noreturn)) 11 | static inline void fatalf(char * restrict fmt, ...) { 12 | va_list args; 13 | va_start(args, fmt); 14 | vprintf(fmt, args); 15 | va_end(args); 16 | exit(1); 17 | } 18 | 19 | static inline void write_byte(struct buffer *buf, uint8_t b) { 20 | if (buf->cap == 0) { 21 | buf->buf = malloc(sizeof(uint8_t) * ++buf->cap); 22 | } else if (buf->len == buf->cap) { 23 | buf->cap *= 2; 24 | buf->buf = realloc(buf->buf, buf->cap * sizeof(uint8_t)); 25 | } 26 | buf->buf[buf->len++] = b; 27 | } 28 | 29 | static inline void write_bytes(struct buffer *buf, uint8_t *bytes, size_t len) { 30 | for (int i = 0; i < len; i++) { 31 | write_byte(buf, bytes[i]); 32 | } 33 | } 34 | 35 | static inline void write_string(struct buffer *buf, const char *str) { 36 | for (int i = 0; str[i] != '\0'; i++) { 37 | write_byte(buf, str[i]); 38 | } 39 | } 40 | 41 | static inline void write_uint32(struct buffer *buf, uint32_t n) { 42 | write_byte(buf, n >> 24); 43 | write_byte(buf, n >> 16); 44 | write_byte(buf, n >> 8); 45 | write_byte(buf, n); 46 | } 47 | 48 | static inline void write_uint64(struct buffer *buf, uint64_t n) { 49 | write_uint32(buf, n >> 32); 50 | write_uint32(buf, n); 51 | } 52 | 53 | static inline void encode_bookmarks(struct buffer *buf, struct bookmark *bookmarks, size_t len) { 54 | for (int i = 0; i < len; i++) { 55 | struct bookmark b = bookmarks[i]; 56 | 57 | write_uint32(buf, b.offset); 58 | write_uint32(buf, b.lineno); 59 | write_uint32(buf, b.pos); 60 | write_uint32(buf, b.len); 61 | write_string(buf, b.line); 62 | } 63 | } 64 | 65 | static inline void encode_objects(struct buffer *buf, struct object *objs, size_t len) { 66 | for (int i = 0; i < len; i++) { 67 | struct object o = objs[i]; 68 | 69 | write_byte(buf, o.type); 70 | switch (o.type) { 71 | case obj_null: 72 | break; 73 | case obj_boolean: 74 | write_byte(buf, o.data.i); 75 | break; 76 | case obj_float: 77 | case obj_integer: 78 | write_uint64(buf, o.data.i); 79 | break; 80 | case obj_string: 81 | write_uint32(buf, o.data.str->len); 82 | write_string(buf, o.data.str->str); 83 | break; 84 | case obj_function: { 85 | struct function *fn = o.data.fn; 86 | write_uint32(buf, fn->num_params); 87 | write_uint32(buf, fn->num_locals); 88 | write_uint32(buf, fn->len); 89 | write_bytes(buf, fn->instructions, fn->len); 90 | write_uint32(buf, fn->bklen); 91 | encode_bookmarks(buf, fn->bookmarks, fn->bklen); 92 | break; 93 | } 94 | default: 95 | fatalf("encoder: unsupported encoding for type %s\n", otype_str(o.type)); 96 | } 97 | } 98 | } 99 | 100 | inline struct buffer tau_encode(struct bytecode bc) { 101 | struct buffer buf = (struct buffer) {0}; 102 | 103 | write_uint32(&buf, bc.ndefs); 104 | write_uint32(&buf, bc.len); 105 | write_bytes(&buf, bc.insts, bc.len); 106 | write_uint32(&buf, bc.nconsts); 107 | encode_objects(&buf, bc.consts, bc.nconsts); 108 | write_uint32(&buf, bc.bklen); 109 | encode_bookmarks(&buf, bc.bookmarks, bc.bklen); 110 | return buf; 111 | } 112 | 113 | inline void free_buffer(struct buffer buf) { 114 | free(buf.buf); 115 | } 116 | 117 | struct reader { 118 | uint8_t *buf; 119 | uint32_t len; 120 | uint32_t pos; 121 | }; 122 | 123 | static inline uint8_t read_byte(struct reader *r) { 124 | if (r->pos == r->len) { 125 | fatalf("decoder: buffer overflow\n"); 126 | } 127 | return r->buf[r->pos++]; 128 | } 129 | 130 | static inline uint32_t read_uint32(struct reader *r) { 131 | return (read_byte(r) << 24) | (read_byte(r) << 16) | (read_byte(r) << 8) | read_byte(r); 132 | } 133 | 134 | static inline uint64_t read_uint64(struct reader *r) { 135 | return (((uint64_t) read_uint32(r)) << 32) | read_uint32(r); 136 | } 137 | 138 | static inline uint8_t *read_bytes(struct reader *r, size_t len) { 139 | uint8_t *b = malloc(sizeof(uint8_t) * len); 140 | 141 | for (int i = 0; i < len; i++) { 142 | b[i] = read_byte(r); 143 | } 144 | return b; 145 | } 146 | 147 | static inline char *read_string(struct reader *r, size_t len) { 148 | char *str = malloc(sizeof(char) * (len + 1)); 149 | str[len] = '\0'; 150 | 151 | for (int i = 0; i < len; i++) { 152 | str[i] = read_byte(r); 153 | } 154 | return str; 155 | } 156 | 157 | static inline struct bookmark *decode_bookmarks(struct reader *r, size_t len) { 158 | struct bookmark *bms = malloc(sizeof(struct bookmark) * len); 159 | 160 | for (int i = 0; i < len; i++) { 161 | bms[i].offset = read_uint32(r); 162 | bms[i].lineno = read_uint32(r); 163 | bms[i].pos = read_uint32(r); 164 | bms[i].len = read_uint32(r); 165 | bms[i].line = read_string(r, bms[i].len); 166 | } 167 | return bms; 168 | } 169 | 170 | static inline struct object *decode_objects(struct reader *r, size_t len) { 171 | struct object *objs = malloc(sizeof(struct object) * len); 172 | 173 | for (int i = 0; i < len; i++) { 174 | enum obj_type type = read_byte(r); 175 | 176 | switch (type) { 177 | case obj_null: 178 | objs[i] = null_obj; 179 | break; 180 | case obj_boolean: 181 | objs[i] = parse_bool(read_byte(r)); 182 | break; 183 | case obj_integer: 184 | objs[i] = new_integer_obj(read_uint64(r)); 185 | break; 186 | case obj_float: 187 | objs[i] = new_float_obj(read_uint64(r)); 188 | break; 189 | case obj_string: { 190 | uint32_t len = read_uint32(r); 191 | objs[i] = new_string_obj(read_string(r, len), len); 192 | break; 193 | } 194 | case obj_function: { 195 | uint32_t nparams = read_uint32(r); 196 | uint32_t nlocals = read_uint32(r); 197 | uint32_t len = read_uint32(r); 198 | uint8_t *insts = read_bytes(r, len); 199 | uint32_t bklen = read_uint32(r); 200 | struct bookmark *bmarks = decode_bookmarks(r, bklen); 201 | objs[i] = new_function_obj(insts, len, nlocals, nparams, bmarks, bklen); 202 | break; 203 | } 204 | default: 205 | fatalf("decoder: unsupported decoding for type %s\n", otype_str(type)); 206 | } 207 | } 208 | return objs; 209 | } 210 | 211 | inline struct bytecode tau_decode(uint8_t *bytes, size_t len) { 212 | if (len == 0) { 213 | fatalf("decoder: empty bytecode"); 214 | } 215 | 216 | struct bytecode bc = (struct bytecode) {0}; 217 | struct reader r = (struct reader) { 218 | .buf = bytes, 219 | .len = len, 220 | .pos = 0 221 | }; 222 | 223 | bc.ndefs = read_uint32(&r); 224 | bc.len = read_uint32(&r); 225 | bc.insts = read_bytes(&r, bc.len); 226 | bc.nconsts = read_uint32(&r); 227 | bc.consts = decode_objects(&r, bc.nconsts); 228 | bc.bklen = read_uint32(&r); 229 | bc.bookmarks = decode_bookmarks(&r, bc.bklen); 230 | return bc; 231 | } 232 | -------------------------------------------------------------------------------- /internal/compiler/scopes_test.go: -------------------------------------------------------------------------------- 1 | package compiler 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/NicoNex/tau/internal/code" 7 | ) 8 | 9 | func TestCompilerScopes(t *testing.T) { 10 | compiler := New() 11 | if compiler.scopeIndex != 0 { 12 | t.Errorf("scopeIndex wrong. got=%d, want=%d", compiler.scopeIndex, 0) 13 | } 14 | globalSymbolTable := compiler.SymbolTable 15 | compiler.Emit(code.OpMul) 16 | compiler.EnterScope() 17 | if compiler.scopeIndex != 1 { 18 | t.Errorf("scopeIndex wrong. got=%d, want=%d", compiler.scopeIndex, 1) 19 | } 20 | compiler.Emit(code.OpSub) 21 | if len(compiler.scopes[compiler.scopeIndex].instructions) != 1 { 22 | t.Errorf("instructions length wrong. got=%d", 23 | len(compiler.scopes[compiler.scopeIndex].instructions)) 24 | } 25 | last := compiler.scopes[compiler.scopeIndex].lastInst 26 | if last.Opcode != code.OpSub { 27 | t.Errorf("lastInst.Opcode wrong. got=%d, want=%d", 28 | last.Opcode, code.OpSub) 29 | } 30 | if compiler.SymbolTable.outer != globalSymbolTable { 31 | t.Errorf("compiler did not enclose symbolTable") 32 | } 33 | compiler.LeaveScope() 34 | if compiler.scopeIndex != 0 { 35 | t.Errorf("scopeIndex wrong. got=%d, want=%d", 36 | compiler.scopeIndex, 0) 37 | } 38 | if compiler.SymbolTable != globalSymbolTable { 39 | t.Errorf("compiler did not restore global symbol table") 40 | } 41 | if compiler.SymbolTable.outer != nil { 42 | t.Errorf("compiler modified global symbol table incorrectly") 43 | } 44 | compiler.Emit(code.OpAdd) 45 | if len(compiler.scopes[compiler.scopeIndex].instructions) != 2 { 46 | t.Errorf("instructions length wrong. got=%d", 47 | len(compiler.scopes[compiler.scopeIndex].instructions)) 48 | } 49 | last = compiler.scopes[compiler.scopeIndex].lastInst 50 | if last.Opcode != code.OpAdd { 51 | t.Errorf("lastInst.Opcode wrong. got=%d, want=%d", 52 | last.Opcode, code.OpAdd) 53 | } 54 | previous := compiler.scopes[compiler.scopeIndex].prevInst 55 | if previous.Opcode != code.OpMul { 56 | t.Errorf("prevInst.Opcode wrong. got=%d, want=%d", 57 | previous.Opcode, code.OpMul) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /internal/compiler/symboltable.go: -------------------------------------------------------------------------------- 1 | package compiler 2 | 3 | type SymbolScope int 4 | 5 | const ( 6 | GlobalScope SymbolScope = iota 7 | LocalScope 8 | BuiltinScope 9 | FreeScope 10 | FunctionScope 11 | ) 12 | 13 | type Symbol struct { 14 | Name string 15 | Scope SymbolScope 16 | Index int 17 | } 18 | 19 | type SymbolTable struct { 20 | outer *SymbolTable 21 | Store map[string]Symbol 22 | FreeSymbols []Symbol 23 | NumDefs int 24 | } 25 | 26 | func NewSymbolTable() *SymbolTable { 27 | return &SymbolTable{Store: make(map[string]Symbol)} 28 | } 29 | 30 | func NewEnclosedSymbolTable(outer *SymbolTable) *SymbolTable { 31 | return &SymbolTable{ 32 | outer: outer, 33 | Store: make(map[string]Symbol), 34 | } 35 | } 36 | 37 | func (s *SymbolTable) Define(name string) Symbol { 38 | if symbol, ok := s.Store[name]; ok { 39 | return symbol 40 | } 41 | 42 | symbol := Symbol{ 43 | Name: name, 44 | Index: s.NumDefs, 45 | Scope: GlobalScope, 46 | } 47 | 48 | if s.outer != nil { 49 | symbol.Scope = LocalScope 50 | } 51 | 52 | s.Store[name] = symbol 53 | s.NumDefs++ 54 | return symbol 55 | } 56 | 57 | func (s *SymbolTable) Resolve(name string) (Symbol, bool) { 58 | obj, ok := s.Store[name] 59 | 60 | if !ok && s.outer != nil { 61 | obj, ok := s.outer.Resolve(name) 62 | if !ok { 63 | return obj, ok 64 | } 65 | 66 | if obj.Scope == GlobalScope || obj.Scope == BuiltinScope { 67 | return obj, ok 68 | } 69 | 70 | return s.DefineFree(obj), true 71 | } 72 | 73 | return obj, ok 74 | } 75 | 76 | func (s *SymbolTable) DefineBuiltin(index int, name string) Symbol { 77 | symbol := Symbol{Name: name, Index: index, Scope: BuiltinScope} 78 | s.Store[name] = symbol 79 | return symbol 80 | } 81 | 82 | func (s *SymbolTable) DefineFree(original Symbol) Symbol { 83 | s.FreeSymbols = append(s.FreeSymbols, original) 84 | symbol := Symbol{ 85 | Name: original.Name, 86 | Index: len(s.FreeSymbols) - 1, 87 | Scope: FreeScope, 88 | } 89 | s.Store[original.Name] = symbol 90 | return symbol 91 | } 92 | 93 | func (s *SymbolTable) DefineFunctionName(n string) Symbol { 94 | symbol := Symbol{Name: n, Index: 0, Scope: FunctionScope} 95 | s.Store[n] = symbol 96 | return symbol 97 | } 98 | -------------------------------------------------------------------------------- /internal/item/item.go: -------------------------------------------------------------------------------- 1 | package item 2 | 3 | import "fmt" 4 | 5 | type Item struct { 6 | Val string 7 | Typ Type 8 | Pos int 9 | } 10 | 11 | func (i Item) Is(t Type) bool { 12 | return i.Typ == t 13 | } 14 | 15 | func (i Item) String() string { 16 | if i.Is(Error) { 17 | return i.Val 18 | } 19 | if len(i.Val) > 10 { 20 | return fmt.Sprintf("%.10q...", i.Val) 21 | } 22 | return fmt.Sprintf("%q", i.Val) 23 | } 24 | -------------------------------------------------------------------------------- /internal/item/type.go: -------------------------------------------------------------------------------- 1 | package item 2 | 3 | type Type int 4 | 5 | const ( 6 | EOF Type = iota 7 | Error 8 | Null 9 | 10 | Ident 11 | Int 12 | Float 13 | String 14 | RawString 15 | 16 | Assign 17 | Plus 18 | Minus 19 | Slash 20 | Asterisk 21 | Modulus 22 | PlusAssign 23 | MinusAssign 24 | AsteriskAssign 25 | SlashAssign 26 | ModulusAssign 27 | BwAndAssign 28 | BwOrAssign 29 | BwXorAssign 30 | LShiftAssign 31 | RShiftAssign 32 | Equals 33 | NotEquals 34 | Bang 35 | LT 36 | GT 37 | LTEQ 38 | GTEQ 39 | And 40 | Or 41 | BwAnd 42 | BwNot 43 | BwOr 44 | BwXor 45 | LShift 46 | RShift 47 | PlusPlus 48 | MinusMinus 49 | 50 | Dot 51 | Comma 52 | Colon 53 | Semicolon 54 | NewLine 55 | 56 | LParen 57 | RParen 58 | 59 | LBrace 60 | RBrace 61 | 62 | LBracket 63 | RBracket 64 | 65 | Function 66 | For 67 | Continue 68 | Break 69 | If 70 | Else 71 | True 72 | False 73 | Return 74 | Import 75 | Tau 76 | ) 77 | 78 | var typemap = map[Type]string{ 79 | EOF: "eof", 80 | Error: "error", 81 | Null: "null", 82 | 83 | Ident: "IDENT", 84 | Int: "int", 85 | Float: "float", 86 | String: "string", 87 | RawString: "rawstring", 88 | 89 | Assign: "=", 90 | Plus: "+", 91 | Minus: "-", 92 | Slash: "/", 93 | Asterisk: "*", 94 | Modulus: "%", 95 | Equals: "==", 96 | NotEquals: "!=", 97 | Bang: "!", 98 | LT: "<", 99 | GT: ">", 100 | LTEQ: "<=", 101 | GTEQ: ">=", 102 | And: "&&", 103 | Or: "||", 104 | PlusAssign: "+=", 105 | MinusAssign: "-=", 106 | AsteriskAssign: "*=", 107 | SlashAssign: "/=", 108 | ModulusAssign: "%=", 109 | BwAndAssign: "&=", 110 | BwOrAssign: "|=", 111 | BwXorAssign: "^=", 112 | LShiftAssign: "<<=", 113 | RShiftAssign: ">>=", 114 | PlusPlus: "++", 115 | MinusMinus: "--", 116 | BwAnd: "&", 117 | BwNot: "~", 118 | BwOr: "|", 119 | BwXor: "^", 120 | LShift: "<<", 121 | RShift: ">>", 122 | 123 | Dot: ".", 124 | Comma: ",", 125 | Colon: ":", 126 | Semicolon: ";", 127 | NewLine: "new line", 128 | 129 | LParen: "(", 130 | RParen: ")", 131 | 132 | LBrace: "{", 133 | RBrace: "}", 134 | 135 | LBracket: "[", 136 | RBracket: "]", 137 | 138 | Function: "function", 139 | For: "for", 140 | Continue: "continue", 141 | Break: "break", 142 | If: "if", 143 | Else: "else", 144 | True: "true", 145 | False: "false", 146 | Return: "return", 147 | Import: "import", 148 | } 149 | 150 | var keywords = map[string]Type{ 151 | "fn": Function, 152 | "for": For, 153 | "continue": Continue, 154 | "break": Break, 155 | "if": If, 156 | "else": Else, 157 | "true": True, 158 | "false": False, 159 | "return": Return, 160 | "null": Null, 161 | "import": Import, 162 | "tau": Tau, 163 | } 164 | 165 | func (t Type) String() string { 166 | return typemap[t] 167 | } 168 | 169 | func Lookup(ident string) Type { 170 | if t, ok := keywords[ident]; ok { 171 | return t 172 | } 173 | return Ident 174 | } 175 | -------------------------------------------------------------------------------- /internal/lexer/lexer_test.go: -------------------------------------------------------------------------------- 1 | package lexer 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/NicoNex/tau/internal/item" 7 | ) 8 | 9 | func TestNextItem(t *testing.T) { 10 | input := ` 11 | five = 5; 12 | ten = 10; 13 | 14 | add = fn(x, y) { 15 | x + y; 16 | }; 17 | 18 | result = add(five, ten); 19 | ! - / * 5 += -= *= /= ; 20 | 5 < 10 > 5; 21 | 22 | if 5 < 10 { 23 | true; 24 | } else { 25 | false; 26 | } 27 | 28 | 10 == 10; 29 | 10 != 9; 30 | "foobar"; 31 | "foo bar"; 32 | [1, 2]; 33 | 34 | fn mul(x, y) { 35 | return x * y; 36 | } 37 | 38 | 10.5 == 10.45; 39 | n = null 40 | {"foo": "bar"} 41 | ` 42 | 43 | tests := []struct { 44 | expTyp item.Type 45 | expLit string 46 | }{ 47 | {item.Ident, "five"}, 48 | {item.Assign, "="}, 49 | {item.Int, "5"}, 50 | {item.Semicolon, ";"}, 51 | 52 | {item.Ident, "ten"}, 53 | {item.Assign, "="}, 54 | {item.Int, "10"}, 55 | {item.Semicolon, ";"}, 56 | 57 | {item.Ident, "add"}, 58 | {item.Assign, "="}, 59 | {item.Function, "fn"}, 60 | {item.LParen, "("}, 61 | {item.Ident, "x"}, 62 | {item.Comma, ","}, 63 | {item.Ident, "y"}, 64 | {item.RParen, ")"}, 65 | {item.LBrace, "{"}, 66 | {item.Ident, "x"}, 67 | {item.Plus, "+"}, 68 | {item.Ident, "y"}, 69 | {item.Semicolon, ";"}, 70 | {item.RBrace, "}"}, 71 | {item.Semicolon, ";"}, 72 | 73 | {item.Ident, "result"}, 74 | {item.Assign, "="}, 75 | {item.Ident, "add"}, 76 | {item.LParen, "("}, 77 | {item.Ident, "five"}, 78 | {item.Comma, ","}, 79 | {item.Ident, "ten"}, 80 | {item.RParen, ")"}, 81 | {item.Semicolon, ";"}, 82 | 83 | {item.Bang, "!"}, 84 | {item.Minus, "-"}, 85 | {item.Slash, "/"}, 86 | {item.Asterisk, "*"}, 87 | {item.Int, "5"}, 88 | {item.PlusAssign, "+="}, 89 | {item.MinusAssign, "-="}, 90 | {item.AsteriskAssign, "*="}, 91 | {item.SlashAssign, "/="}, 92 | {item.Semicolon, ";"}, 93 | 94 | {item.Int, "5"}, 95 | {item.LT, "<"}, 96 | {item.Int, "10"}, 97 | {item.GT, ">"}, 98 | {item.Int, "5"}, 99 | {item.Semicolon, ";"}, 100 | 101 | {item.If, "if"}, 102 | {item.Int, "5"}, 103 | {item.LT, "<"}, 104 | {item.Int, "10"}, 105 | {item.LBrace, "{"}, 106 | {item.True, "true"}, 107 | {item.Semicolon, ";"}, 108 | {item.RBrace, "}"}, 109 | {item.Else, "else"}, 110 | {item.LBrace, "{"}, 111 | {item.False, "false"}, 112 | {item.Semicolon, ";"}, 113 | {item.RBrace, "}"}, 114 | {item.Semicolon, "\n"}, 115 | 116 | {item.Int, "10"}, 117 | {item.Equals, "=="}, 118 | {item.Int, "10"}, 119 | {item.Semicolon, ";"}, 120 | 121 | {item.Int, "10"}, 122 | {item.NotEquals, "!="}, 123 | {item.Int, "9"}, 124 | {item.Semicolon, ";"}, 125 | 126 | {item.String, "foobar"}, 127 | {item.Semicolon, ";"}, 128 | 129 | {item.String, "foo bar"}, 130 | {item.Semicolon, ";"}, 131 | 132 | {item.LBracket, "["}, 133 | {item.Int, "1"}, 134 | {item.Comma, ","}, 135 | {item.Int, "2"}, 136 | {item.RBracket, "]"}, 137 | {item.Semicolon, ";"}, 138 | 139 | {item.Function, "fn"}, 140 | {item.Ident, "mul"}, 141 | {item.LParen, "("}, 142 | {item.Ident, "x"}, 143 | {item.Comma, ","}, 144 | {item.Ident, "y"}, 145 | {item.RParen, ")"}, 146 | {item.LBrace, "{"}, 147 | {item.Return, "return"}, 148 | {item.Ident, "x"}, 149 | {item.Asterisk, "*"}, 150 | {item.Ident, "y"}, 151 | {item.Semicolon, ";"}, 152 | {item.RBrace, "}"}, 153 | {item.Semicolon, "\n"}, 154 | 155 | {item.Float, "10.5"}, 156 | {item.Equals, "=="}, 157 | {item.Float, "10.45"}, 158 | {item.Semicolon, ";"}, 159 | 160 | {item.Ident, "n"}, 161 | {item.Assign, "="}, 162 | {item.Null, "null"}, 163 | {item.Semicolon, "\n"}, 164 | 165 | {item.LBrace, "{"}, 166 | {item.String, "foo"}, 167 | {item.Colon, ":"}, 168 | {item.String, "bar"}, 169 | {item.RBrace, "}"}, 170 | {item.Semicolon, "\n"}, 171 | 172 | {item.EOF, ""}, 173 | } 174 | 175 | items := Lex(input) 176 | 177 | i := 0 178 | for itm := range items { 179 | t.Log(itm.Typ, itm.Val) 180 | if i >= len(tests) { 181 | break 182 | } 183 | 184 | tt := tests[i] 185 | if itm.Typ != tt.expTyp { 186 | t.Fatalf("tests[%d] - wrong item type: expected=%s, got=%s", i, tt.expTyp, itm.Typ) 187 | } 188 | if itm.Val != tt.expLit { 189 | t.Fatalf("tests[%d] - wrong item literal: expected=%q, got=%q", i, tt.expLit, itm.Val) 190 | } 191 | i++ 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /internal/obj/boolean.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "object.h" 3 | 4 | struct object true_obj = (struct object) { 5 | .data.i = 1, 6 | .type = obj_boolean, 7 | .marked = NULL, 8 | }; 9 | 10 | struct object false_obj = (struct object) { 11 | .data.i = 0, 12 | .type = obj_boolean, 13 | .marked = NULL, 14 | }; 15 | 16 | inline __attribute__((always_inline)) 17 | struct object parse_bool(uint32_t b) { 18 | return b ? true_obj : false_obj; 19 | } 20 | 21 | char *boolean_str(struct object o) { 22 | return o.data.i ? strdup("true") : strdup("false"); 23 | } 24 | -------------------------------------------------------------------------------- /internal/obj/bytes.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "object.h" 5 | 6 | void dispose_bytes_obj(struct object o) { 7 | // Free everything if it's not a slice (marked parent bit is set to NULL). 8 | if (o.data.str->m_parent == NULL) { 9 | free(o.marked); 10 | free(o.data.bytes->bytes); 11 | } 12 | free(o.data.bytes); 13 | } 14 | 15 | char *bytes_str(struct object o) { 16 | size_t slen = o.data.bytes->len * 5 + 3; 17 | char *s = calloc(slen, sizeof(char)); 18 | s[0] = '['; 19 | 20 | char tmp[4] = {'\0'}; 21 | size_t blen = o.data.bytes->len; 22 | 23 | for (uint32_t i = 0; i < blen; i++) { 24 | snprintf(tmp, 4, "%u", o.data.bytes->bytes[i]); 25 | strcat(s, tmp); 26 | if (i < blen-1) strcat(s, ", "); 27 | } 28 | strcat(s, "]"); 29 | return s; 30 | } 31 | 32 | struct object new_bytes_obj(uint8_t *bytes, size_t len) { 33 | struct bytes *b = malloc(sizeof(struct bytes)); 34 | b->bytes = bytes; 35 | b->len = len; 36 | b->m_parent = NULL; 37 | 38 | return (struct object) { 39 | .data.bytes = b, 40 | .type = obj_bytes, 41 | .marked = MARKPTR(), 42 | }; 43 | } 44 | 45 | void mark_bytes_obj(struct object b) { 46 | *b.marked = 1; 47 | if (b.data.bytes->m_parent != NULL) { 48 | *b.data.bytes->m_parent = 1; 49 | } 50 | } 51 | 52 | struct object new_bytes_slice(uint8_t *bytes, size_t len, uint32_t *m_parent) { 53 | struct bytes *b = malloc(sizeof(struct bytes)); 54 | b->bytes = bytes; 55 | b->len = len; 56 | b->m_parent = m_parent; 57 | 58 | return (struct object) { 59 | .data.bytes = b, 60 | .type = obj_bytes, 61 | .marked = MARKPTR(), 62 | }; 63 | } 64 | -------------------------------------------------------------------------------- /internal/obj/closure.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "object.h" 4 | 5 | void dispose_closure_obj(struct object o) { 6 | dispose_function_data(o.data.cl->fn); 7 | free(o.marked); 8 | free(o.data.cl->free); 9 | free(o.data.cl); 10 | } 11 | 12 | char *closure_str(struct object o) { 13 | char *str = calloc(35, sizeof(char)); 14 | sprintf(str, "closure[%p]", o.data.cl->fn); 15 | 16 | return str; 17 | } 18 | 19 | void mark_closure_obj(struct object c) { 20 | *c.marked = 1; 21 | for (uint32_t i = 0; i < c.data.cl->num_free; i++) { 22 | mark_obj(c.data.cl->free[i]); 23 | } 24 | } 25 | 26 | struct object new_closure_obj(struct function *fn, struct object *free, size_t num_free) { 27 | struct closure *cl = malloc(sizeof(struct closure)); 28 | cl->fn = fn; 29 | cl->free = free; 30 | cl->num_free = num_free; 31 | 32 | return (struct object) { 33 | .data.cl = cl, 34 | .type = obj_closure, 35 | .marked = MARKPTR(), 36 | }; 37 | } 38 | -------------------------------------------------------------------------------- /internal/obj/error.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "object.h" 6 | 7 | void dispose_error_obj(struct object o) { 8 | free(o.marked); 9 | free(o.data.str->str); 10 | free(o.data.str); 11 | } 12 | 13 | char *error_str(struct object o) { 14 | return strdup(o.data.str->str); 15 | } 16 | 17 | struct object new_error_obj(char *str, size_t len) { 18 | struct string *s = malloc(sizeof(struct string)); 19 | s->str = str; 20 | s->len = len; 21 | 22 | return (struct object) { 23 | .data.str = s, 24 | .type = obj_error, 25 | .marked = MARKPTR(), 26 | }; 27 | } 28 | 29 | inline struct object errorf(char *fmt, ...) { 30 | char *msg = malloc(sizeof(char) * 256); 31 | msg[255] = '\0'; 32 | 33 | va_list ap; 34 | va_start(ap, fmt); 35 | vsnprintf(msg, 256, fmt, ap); 36 | va_end(ap); 37 | 38 | return new_error_obj(msg, strlen(msg)); 39 | } 40 | -------------------------------------------------------------------------------- /internal/obj/float.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "object.h" 4 | 5 | char *float_str(struct object o) { 6 | char *str = calloc(35, sizeof(char)); 7 | sprintf(str, "%f", o.data.f); 8 | 9 | return str; 10 | } 11 | 12 | struct object new_float_obj(double val) { 13 | return (struct object) { 14 | .data.f = val, 15 | .type = obj_float, 16 | .marked = NULL, 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /internal/obj/function.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "object.h" 4 | 5 | inline void dispose_function_data(struct function *fn) { 6 | for (int i = 0; i < fn->bklen; i++) { 7 | free(fn->bookmarks[i].line); 8 | } 9 | free(fn->bookmarks); 10 | free(fn->instructions); 11 | free(fn); 12 | } 13 | 14 | void dispose_function_obj(struct object o) { 15 | dispose_function_data(o.data.fn); 16 | free(o.marked); 17 | } 18 | 19 | char *function_str(struct object o) { 20 | char *str = calloc(35, sizeof(char)); 21 | sprintf(str, "closure[%p]", o.data.fn); 22 | 23 | return str; 24 | } 25 | 26 | inline struct function *new_function(uint8_t *insts, size_t len, uint32_t num_locals, uint32_t num_params, struct bookmark *bmarks, uint32_t bklen) { 27 | struct function *fn = malloc(sizeof(struct function)); 28 | fn->instructions = insts; 29 | fn->len = len; 30 | fn->num_locals = num_locals; 31 | fn->num_params = num_params; 32 | fn->bookmarks = bmarks; 33 | fn->bklen = bklen; 34 | 35 | return fn; 36 | } 37 | 38 | inline struct object new_function_obj(uint8_t *insts, size_t len, uint32_t num_locals, uint32_t num_params, struct bookmark *bmarks, uint32_t bklen) { 39 | return (struct object) { 40 | .data.fn = new_function(insts, len, num_locals, num_params, bmarks, bklen), 41 | .type = obj_function, 42 | .marked = MARKPTR(), 43 | }; 44 | } 45 | -------------------------------------------------------------------------------- /internal/obj/integer.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "object.h" 5 | 6 | char *integer_str(struct object o) { 7 | char *str = calloc(64, sizeof(char)); 8 | sprintf(str, "%" PRId64, o.data.i); 9 | 10 | return str; 11 | } 12 | 13 | struct object new_integer_obj(int64_t val) { 14 | return (struct object) { 15 | .data.i = val, 16 | .type = obj_integer, 17 | .marked = NULL, 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /internal/obj/list.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "object.h" 4 | 5 | void dispose_list_obj(struct object o) { 6 | // Free everything if it's not a slice (marked parent bit is set to NULL). 7 | if (o.data.list->m_parent == NULL) { 8 | free(o.data.list->list); 9 | } 10 | free(o.data.list); 11 | free(o.marked); 12 | } 13 | 14 | // TODO: optimise this. 15 | char *list_str(struct object o) { 16 | size_t len = o.data.list->len; 17 | struct object *list = o.data.list->list; 18 | char *strings[len]; 19 | size_t string_len = 3; 20 | 21 | for (int i = 0; i < len; i++) { 22 | char *s = object_str(list[i]); 23 | strings[i] = s; 24 | string_len += i < len-1 ? strlen(s) + 2 : strlen(s); 25 | } 26 | 27 | char *str = calloc(string_len, sizeof(char)); 28 | str[0] = '['; 29 | 30 | for (int i = 0; i < len; i++) { 31 | strcat(str, strings[i]); 32 | if (i < len-1) strcat(str, ", "); 33 | free(strings[i]); 34 | } 35 | strcat(str, "]"); 36 | 37 | return str; 38 | } 39 | 40 | void mark_list_obj(struct object l) { 41 | *l.marked = 1; 42 | if (l.data.list->m_parent != NULL) { 43 | *l.data.list->m_parent = 1; 44 | } 45 | #pragma omp parallel for 46 | for (uint32_t i = 0; i < l.data.list->len; i++) { 47 | mark_obj(l.data.list->list[i]); 48 | } 49 | } 50 | 51 | struct object make_list(size_t cap) { 52 | struct list *l = malloc(sizeof(struct list)); 53 | l->list = calloc(cap, sizeof(struct object)); 54 | l->len = 0; 55 | l->cap = cap; 56 | l->m_parent = NULL; 57 | 58 | return (struct object) { 59 | .data.list = l, 60 | .type = obj_list, 61 | .marked = MARKPTR() 62 | }; 63 | } 64 | 65 | struct object new_list_obj(struct object *list, size_t len) { 66 | struct list *l = malloc(sizeof(struct list)); 67 | l->list = list; 68 | l->len = len; 69 | l->cap = len; 70 | l->m_parent = NULL; 71 | 72 | return (struct object) { 73 | .data.list = l, 74 | .type = obj_list, 75 | .marked = MARKPTR() 76 | }; 77 | } 78 | 79 | struct object new_list_obj_data(struct object *list, size_t len, size_t cap) { 80 | struct list *l = malloc(sizeof(struct list)); 81 | l->list = list; 82 | l->len = len; 83 | l->cap = cap; 84 | l->m_parent = NULL; 85 | 86 | return (struct object) { 87 | .data.list = l, 88 | .type = obj_list, 89 | .marked = MARKPTR() 90 | }; 91 | } 92 | 93 | struct object new_list_slice(struct object *list, size_t len, uint32_t *m_parent) { 94 | struct list *l = malloc(sizeof(struct list)); 95 | l->list = list; 96 | l->len = len; 97 | l->cap = len; 98 | l->m_parent = m_parent; 99 | 100 | return (struct object) { 101 | .data.list = l, 102 | .type = obj_list, 103 | .marked = MARKPTR(), 104 | }; 105 | } 106 | 107 | inline struct list list_copy(struct list l) { 108 | struct list ret = { 109 | .list = malloc(sizeof(struct object) * l.cap), 110 | .cap = l.cap, 111 | .len = l.len 112 | }; 113 | memcpy(ret.list, l.list, l.cap); 114 | 115 | return ret; 116 | } 117 | -------------------------------------------------------------------------------- /internal/obj/map.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "object.h" 6 | 7 | // Taken from: https://github.com/haipome/fnv/blob/master/fnv.c#L368 8 | inline uint64_t fnv64a(char *s) { 9 | uint64_t hash = 0xcbf29ce484222325ULL; 10 | 11 | while (*s) { 12 | hash ^= (uint64_t)*s++; 13 | #if defined(NO_FNV_GCC_OPTIMIZATION) 14 | hash *= FNV_64_PRIME; 15 | #else 16 | hash += (hash << 1) + (hash << 4) + (hash << 5) + 17 | (hash << 7) + (hash << 8) + (hash << 40); 18 | #endif 19 | } 20 | return hash; 21 | } 22 | 23 | static inline uint64_t dtoi(double d) { 24 | uint64_t i; 25 | memcpy(&i, &d, sizeof(double)); 26 | return i; 27 | } 28 | 29 | struct key_hash hash(struct object o) { 30 | switch (o.type) { 31 | case obj_integer: 32 | case obj_boolean: 33 | return (struct key_hash) { 34 | .type = o.type, 35 | .val = o.data.i 36 | }; 37 | case obj_error: 38 | case obj_string: 39 | return (struct key_hash) { 40 | .type = o.type, 41 | .val = fnv64a(o.data.str->str) 42 | }; 43 | case obj_float: 44 | return (struct key_hash) { 45 | .type = o.type, 46 | .val = dtoi(o.data.f) 47 | }; 48 | default: 49 | return (struct key_hash) {0}; 50 | } 51 | } 52 | 53 | static inline struct map_pair _map_get(struct map_node * restrict n, struct key_hash k) { 54 | if (n == NULL) { 55 | return (struct map_pair) { 56 | .key = null_obj, 57 | .val = null_obj 58 | }; 59 | } 60 | 61 | int cmp = memcmp(&k, &n->key, sizeof(struct key_hash)); 62 | if (cmp == 0) { 63 | return n->val; 64 | } else if (cmp < 0) { 65 | return _map_get(n->l, k); 66 | } else { 67 | return _map_get(n->r, k); 68 | } 69 | } 70 | 71 | static void mark_map_children(struct map_node * restrict n) { 72 | if (n != NULL) { 73 | mark_obj(n->val.key); 74 | mark_obj(n->val.val); 75 | mark_map_children(n->l); 76 | mark_map_children(n->r); 77 | } 78 | } 79 | 80 | static inline void _map_set(struct map_node **n, struct key_hash k, struct map_pair v) { 81 | if (*n == NULL) { 82 | struct map_node *tmp = malloc(sizeof(struct map_node)); 83 | tmp->key = k; 84 | tmp->val = v; 85 | tmp->l = NULL; 86 | tmp->r = NULL; 87 | *n = tmp; 88 | return; 89 | } 90 | 91 | int cmp = memcmp(&k, &(*n)->key, sizeof(struct key_hash)); 92 | if (cmp == 0) { 93 | (*n)->key = k; 94 | (*n)->val = v; 95 | } else if (cmp < 0) { 96 | _map_set(&(*n)->l, k, v); 97 | } else { 98 | _map_set(&(*n)->r, k, v); 99 | } 100 | } 101 | 102 | static inline void map_set_node(struct map_node **root, struct map_node **cur, struct map_node *n) { 103 | if (*cur == NULL) { 104 | *cur = n; 105 | return; 106 | } 107 | 108 | int cmp = memcmp(&(*cur)->key, &n->key, sizeof(struct key_hash)); 109 | if (cmp == 0) { 110 | struct map_node *l = (*cur)->l; 111 | struct map_node *r = (*cur)->r; 112 | 113 | free(*cur); 114 | *cur = n; 115 | if (l != NULL) map_set_node(root, root, l); 116 | if (r != NULL) map_set_node(root, root, r); 117 | } else if (cmp < 0) { 118 | map_set_node(root, &(*cur)->l, n); 119 | } else { 120 | map_set_node(root, &(*cur)->r, n); 121 | } 122 | } 123 | 124 | static inline void _map_delete(struct map_node **root, struct map_node **n, struct key_hash k) { 125 | if (*n != NULL) { 126 | struct map_node *node = *n; 127 | int cmp = memcmp(&k, &node->key, sizeof(struct key_hash)); 128 | 129 | if (cmp == 0) { 130 | *n = NULL; 131 | if (node->l) map_set_node(root, root, node->l); 132 | if (node->r) map_set_node(root, root, node->r); 133 | free(node); 134 | } else if (cmp < 0) { 135 | _map_delete(root, &(*n)->l, k); 136 | } else { 137 | _map_delete(root, &(*n)->r, k); 138 | } 139 | } 140 | } 141 | 142 | static inline void _map_dispose(struct map_node * restrict n) { 143 | if (n != NULL) { 144 | if (n->l != NULL) _map_dispose(n->l); 145 | if (n->r != NULL) _map_dispose(n->r); 146 | free(n); 147 | } 148 | } 149 | 150 | static inline void _map_keys(struct map_node * restrict n, struct list *list) { 151 | if (n != NULL) { 152 | list->list[list->len++] = n->val.key; 153 | _map_keys(n->l, list); 154 | _map_keys(n->r, list); 155 | } 156 | } 157 | 158 | struct object map_keys(struct object map) { 159 | struct object list = make_list(map.data.map->len); 160 | 161 | _map_keys(map.data.map->root, list.data.list); 162 | return list; 163 | } 164 | 165 | struct map_pair map_get(struct object map, struct object o) { 166 | return _map_get(map.data.map->root, hash(o)); 167 | } 168 | 169 | struct map_pair map_set(struct object map, struct object k, struct object v) { 170 | struct map_pair p = (struct map_pair) {.key = k, .val = v}; 171 | 172 | _map_set(&map.data.map->root, hash(k), p); 173 | map.data.map->len++; 174 | return p; 175 | } 176 | 177 | void map_delete(struct object map, struct object key) { 178 | _map_delete(&map.data.map->root, &map.data.map->root, hash(key)); 179 | map.data.map->len--; 180 | } 181 | 182 | void dispose_map_obj(struct object map) { 183 | _map_dispose(map.data.map->root); 184 | free(map.data.map); 185 | } 186 | 187 | // TODO: actually return map content as string. 188 | char *map_str(struct object map) { 189 | char *str = malloc(sizeof(char) * 64); 190 | str[63] = '\0'; 191 | sprintf(str, "map[%p]", map.data.map->root); 192 | 193 | return str; 194 | } 195 | 196 | struct object new_map() { 197 | return (struct object) { 198 | .data.map = calloc(1, sizeof(struct map_node)), 199 | .type = obj_map, 200 | .marked = MARKPTR() 201 | }; 202 | } 203 | 204 | void mark_map_obj(struct object m) { 205 | *m.marked = 1; 206 | mark_map_children(m.data.map->root); 207 | } 208 | -------------------------------------------------------------------------------- /internal/obj/null.c: -------------------------------------------------------------------------------- 1 | #include "object.h" 2 | 3 | struct object null_obj = (struct object) { 4 | .data.i = 0, 5 | .type = obj_null, 6 | .marked = NULL, 7 | }; 8 | -------------------------------------------------------------------------------- /internal/obj/object.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "object.h" 7 | 8 | static inline struct object _object_get(struct object_node * restrict n, uint64_t key) { 9 | if (n == NULL) { 10 | return null_obj; 11 | } 12 | 13 | if (key == n->key) { 14 | return n->val; 15 | } else if (key < n->key) { 16 | return _object_get(n->l, key); 17 | } else { 18 | return _object_get(n->r, key); 19 | } 20 | } 21 | 22 | static void mark_object_children(struct object_node * restrict n) { 23 | if (n != NULL) { 24 | mark_obj(n->val); 25 | mark_object_children(n->l); 26 | mark_object_children(n->r); 27 | } 28 | } 29 | 30 | struct object object_to_module(struct object o); 31 | 32 | static void _object_to_module(struct object mod, struct object_node * restrict n) { 33 | if (n != NULL) { 34 | if (isupper(*n->name)) { 35 | if (n->val.type == obj_object) { 36 | object_set(mod, n->name, object_to_module(n->val)); 37 | } else { 38 | object_set(mod, n->name, n->val); 39 | } 40 | } 41 | _object_to_module(mod, n->l); 42 | _object_to_module(mod, n->r); 43 | } 44 | } 45 | 46 | static inline void _object_set(struct object_node **n, uint64_t key, char *name, struct object val) { 47 | if (*n == NULL) { 48 | *n = malloc(sizeof(struct object_node)); 49 | (*n)->name = strdup(name); 50 | (*n)->key = key; 51 | (*n)->val = val; 52 | (*n)->l = NULL; 53 | (*n)->r = NULL; 54 | return; 55 | } 56 | 57 | uint64_t cur = (*n)->key; 58 | if (key == cur) { 59 | (*n)->val = val; 60 | } else if (key < cur) { 61 | _object_set(&(*n)->l, key, name, val); 62 | } else { 63 | _object_set(&(*n)->r, key, name, val); 64 | } 65 | } 66 | 67 | static inline void _object_dispose(struct object_node * restrict n) { 68 | if (n != NULL) { 69 | if (n->l != NULL) _object_dispose(n->l); 70 | if (n->r != NULL) _object_dispose(n->r); 71 | free(n->name); 72 | free(n); 73 | } 74 | } 75 | 76 | struct object object_get(struct object obj, char *name) { 77 | return _object_get(*obj.data.obj, fnv64a(name)); 78 | } 79 | 80 | struct object object_set(struct object obj, char *name, struct object val) { 81 | _object_set(obj.data.obj, fnv64a(name), name, val); 82 | return val; 83 | } 84 | 85 | void dispose_object_obj(struct object obj) { 86 | _object_dispose(*obj.data.obj); 87 | free(obj.marked); 88 | free(obj.data.obj); 89 | } 90 | 91 | // TODO: actually return object content as string. 92 | char *object_obj_str(struct object obj) { 93 | char *str = malloc(sizeof(char) * 64); 94 | str[63] = '\0'; 95 | sprintf(str, "object[%p]", *obj.data.obj); 96 | 97 | return str; 98 | } 99 | 100 | struct object new_object() { 101 | return (struct object) { 102 | .data.obj = calloc(1, sizeof(struct object_node *)), 103 | .type = obj_object, 104 | .marked = MARKPTR(), 105 | }; 106 | } 107 | 108 | struct object object_to_module(struct object o) { 109 | struct object mod = new_object(); 110 | 111 | _object_to_module(mod, *o.data.obj); 112 | return mod; 113 | } 114 | 115 | void mark_object_obj(struct object o) { 116 | *o.marked = 1; 117 | mark_object_children(*o.data.obj); 118 | } 119 | -------------------------------------------------------------------------------- /internal/obj/object.go: -------------------------------------------------------------------------------- 1 | package obj 2 | 3 | /* 4 | #cgo CFLAGS: -Ofast -Ilibffi/include 5 | #cgo LDFLAGS: -Llibffi/lib ${SRCDIR}/libffi/lib/libffi.a -lm 6 | #include 7 | #include 8 | #include 9 | #include "object.h" 10 | 11 | static inline uint32_t is_error(struct object o) { 12 | return o.type == obj_error; 13 | } 14 | 15 | static inline char *error_msg(struct object err) { 16 | return err.data.str->str; 17 | } 18 | 19 | static inline int64_t int_val(struct object i) { 20 | return i.data.i; 21 | } 22 | 23 | static inline double float_val(struct object f) { 24 | return f.data.f; 25 | } 26 | 27 | static inline struct function *function_val(struct object fn) { 28 | return fn.data.fn; 29 | } 30 | 31 | static void set_stdout(int fd, const char *name) { 32 | #if !defined(_WIN32) && !defined(WIN32) 33 | stdout = fdopen(fd, name); 34 | #endif 35 | } 36 | */ 37 | import "C" 38 | 39 | import ( 40 | "errors" 41 | "fmt" 42 | "io" 43 | "os" 44 | "unsafe" 45 | 46 | "github.com/NicoNex/tau/internal/code" 47 | "github.com/NicoNex/tau/internal/tauerr" 48 | ) 49 | 50 | type ( 51 | Object = C.struct_object 52 | Type = C.enum_obj_type 53 | CompiledFunction = C.struct_function 54 | ) 55 | 56 | const ( 57 | NullType Type = C.obj_null // null 58 | BoolType = C.obj_boolean // bool 59 | IntType = C.obj_integer // int 60 | FloatType = C.obj_float // float 61 | BuiltinType = C.obj_builtin // builtin 62 | StringType = C.obj_string // string 63 | ErrorType = C.obj_error // error 64 | ListType = C.obj_list // list 65 | MapType = C.obj_map // map 66 | FunctionType = C.obj_function // function 67 | ClosureType = C.obj_closure // closure 68 | ObjectType = C.obj_object // object 69 | PipeType = C.obj_pipe // pipe 70 | BytesType = C.obj_bytes // bytes 71 | NativeType = C.obj_native // native 72 | ) 73 | 74 | var ( 75 | Stdout io.Writer = os.Stdout 76 | Stdin io.Reader = os.Stdin 77 | 78 | Builtins = [...]string{ 79 | "len", 80 | "println", 81 | "print", 82 | "input", 83 | "string", 84 | "error", 85 | "type", 86 | "int", 87 | "float", 88 | "exit", 89 | "append", 90 | "new", 91 | "failed", 92 | "plugin", 93 | "pipe", 94 | "send", 95 | "recv", 96 | "close", 97 | "hex", 98 | "oct", 99 | "bin", 100 | "slice", 101 | "keys", 102 | "delete", 103 | "bytes", 104 | } 105 | 106 | NullObj = C.null_obj 107 | TrueObj = C.true_obj 108 | FalseObj = C.false_obj 109 | ) 110 | 111 | func (o Object) Type() Type { 112 | return o._type 113 | } 114 | 115 | func (o Object) TypeString() string { 116 | return C.GoString(C.otype_str(o._type)) 117 | } 118 | 119 | func (o Object) String() string { 120 | cstr := C.object_str(o) 121 | defer C.free(unsafe.Pointer(cstr)) 122 | return C.GoString(cstr) 123 | } 124 | 125 | func (o Object) Int() int64 { 126 | return int64(C.int_val(o)) 127 | } 128 | 129 | func (o Object) Float() float64 { 130 | return float64(C.float_val(o)) 131 | } 132 | 133 | func (o Object) CompiledFunction() *CompiledFunction { 134 | return C.function_val(o) 135 | } 136 | 137 | func (o Object) IsTruthy() bool { 138 | return C.is_truthy(&o) == 1 139 | } 140 | 141 | func (cf CompiledFunction) Instructions() []byte { 142 | return C.GoBytes(unsafe.Pointer(cf.instructions), C.int(cf.len)) 143 | } 144 | 145 | func (cf CompiledFunction) Len() int { 146 | return int(cf.len) 147 | } 148 | 149 | func (cf CompiledFunction) NumLocals() int { 150 | return int(cf.num_locals) 151 | } 152 | 153 | func (cf CompiledFunction) NumParams() int { 154 | return int(cf.num_params) 155 | } 156 | 157 | func (cf CompiledFunction) BKLen() int { 158 | return int(cf.bklen) 159 | } 160 | 161 | func ParseBool(b bool) Object { 162 | if b { 163 | return TrueObj 164 | } 165 | return FalseObj 166 | } 167 | 168 | func IsError(o Object) bool { 169 | return C.is_error(o) == 1 170 | } 171 | 172 | func GoError(o Object) error { 173 | if IsError(o) { 174 | return errors.New(C.GoString(C.error_msg(o))) 175 | } 176 | return nil 177 | } 178 | 179 | func NewBool(b bool) Object { 180 | if b { 181 | return C.true_obj 182 | } else { 183 | return C.false_obj 184 | } 185 | } 186 | 187 | func NewInteger(i int64) Object { 188 | return C.new_integer_obj(C.int64_t(i)) 189 | } 190 | 191 | func NewFloat(f float64) Object { 192 | return C.new_float_obj(C.double(f)) 193 | } 194 | 195 | func NewString(s string) Object { 196 | return C.new_string_obj(C.CString(s), C.size_t(len(s))) 197 | } 198 | 199 | func CArray[CT, GoT any](s []GoT) *CT { 200 | if len(s) > 0 { 201 | return (*CT)(unsafe.Pointer(&s[0])) 202 | } 203 | return nil 204 | } 205 | 206 | func NewFunctionCompiled(ins code.Instructions, nlocals, nparams int, bmarks []tauerr.Bookmark) Object { 207 | return C.new_function_obj( 208 | (*C.uchar)(unsafe.Pointer(&ins[0])), 209 | C.size_t(len(ins)), 210 | C.uint(nlocals), 211 | C.uint(nparams), 212 | CArray[C.struct_bookmark, tauerr.Bookmark](bmarks), 213 | C.uint(len(bmarks)), 214 | ) 215 | } 216 | 217 | func AssertTypes(o Object, types ...Type) bool { 218 | for _, t := range types { 219 | if t == o.Type() { 220 | return true 221 | } 222 | } 223 | return false 224 | } 225 | 226 | func ToFloat(l, r Object) (left, right float64) { 227 | left, right = l.Float(), r.Float() 228 | 229 | if l.Type() == IntType { 230 | left = float64(l.Int()) 231 | } 232 | if r.Type() == IntType { 233 | right = float64(r.Int()) 234 | } 235 | return 236 | } 237 | 238 | func Println(a ...any) { 239 | fmt.Fprintln(Stdout, a...) 240 | } 241 | 242 | func Printf(s string, a ...any) { 243 | fmt.Fprintf(Stdout, s, a...) 244 | } 245 | 246 | func SetStdout(fd int, name string) { 247 | C.set_stdout(C.int(fd), C.CString(name)) 248 | } 249 | -------------------------------------------------------------------------------- /internal/obj/object.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include "../vm/thrd.h" 7 | #include "../tauerr/bookmark.h" 8 | 9 | #define NUM_BUILTINS 26 10 | #define MARKPTR() calloc(1, sizeof(uint32_t)) 11 | 12 | enum obj_type { 13 | obj_null, 14 | obj_boolean, 15 | obj_integer, 16 | obj_float, 17 | obj_builtin, 18 | obj_string, 19 | obj_error, 20 | obj_list, 21 | obj_map, 22 | obj_function, 23 | obj_closure, 24 | obj_object, 25 | obj_pipe, 26 | obj_bytes, 27 | obj_native 28 | }; 29 | 30 | struct function { 31 | uint8_t *instructions; 32 | size_t len; 33 | uint32_t num_locals; 34 | uint32_t num_params; 35 | uint32_t bklen; 36 | struct bookmark *bookmarks; 37 | }; 38 | 39 | struct closure { 40 | struct function *fn; 41 | struct object *free; 42 | size_t num_free; 43 | }; 44 | 45 | struct map { 46 | struct map_node *root; 47 | size_t len; 48 | }; 49 | 50 | struct list { 51 | struct object *list; 52 | size_t len; 53 | size_t cap; 54 | uint32_t *m_parent; 55 | }; 56 | 57 | struct string { 58 | char *str; 59 | size_t len; 60 | uint32_t *m_parent; 61 | }; 62 | 63 | struct bytes { 64 | uint8_t *bytes; 65 | size_t len; 66 | uint32_t *m_parent; 67 | }; 68 | 69 | struct pipe { 70 | struct object *buf; 71 | size_t cap; 72 | size_t len; 73 | uint32_t head; 74 | uint32_t tail; 75 | uint32_t is_buffered; 76 | uint32_t is_closed; 77 | mtx_t mu; 78 | cnd_t not_empty; 79 | cnd_t not_full; 80 | }; 81 | 82 | union data { 83 | int64_t i; 84 | double f; 85 | void *handle; 86 | struct function *fn; 87 | struct closure *cl; 88 | struct string *str; 89 | struct bytes *bytes; 90 | struct list *list; 91 | struct map *map; 92 | struct object_node **obj; 93 | struct pipe *pipe; 94 | struct object (*builtin)(struct object *args, size_t len); 95 | }; 96 | 97 | struct object { 98 | union data data; 99 | enum obj_type type; 100 | uint32_t *marked; 101 | }; 102 | 103 | struct key_hash { 104 | uint64_t type; 105 | uint64_t val; 106 | } __attribute__((packed)); 107 | 108 | struct map_pair { 109 | struct object key; 110 | struct object val; 111 | }; 112 | 113 | struct map_node { 114 | struct key_hash key; 115 | struct map_pair val; 116 | struct map_node *l; 117 | struct map_node *r; 118 | }; 119 | 120 | struct object_node { 121 | char *name; 122 | uint64_t key; 123 | struct object val; 124 | struct object_node *l; 125 | struct object_node *r; 126 | }; 127 | 128 | // Static objects. 129 | extern struct object null_obj; 130 | extern struct object true_obj; 131 | extern struct object false_obj; 132 | 133 | // Boolean object. 134 | struct object new_boolean_obj(uint32_t b); 135 | struct object parse_bool(uint32_t b); 136 | char *boolean_str(struct object o); 137 | 138 | // Integer object. 139 | struct object new_integer_obj(int64_t val); 140 | char *integer_str(struct object o); 141 | 142 | // Float object. 143 | struct object new_float_obj(double val); 144 | char *float_str(struct object o); 145 | 146 | // String object. 147 | struct object new_string_obj(char *str, size_t len); 148 | struct object new_string_slice(char *str, size_t len, uint32_t *m_parent); 149 | char *string_str(struct object o); 150 | void mark_string_obj(struct object s); 151 | void dispose_string_obj(struct object o); 152 | 153 | // Bytes object. 154 | struct object new_bytes_obj(uint8_t *bytes, size_t len); 155 | struct object new_bytes_slice(uint8_t *bytes, size_t len, uint32_t *m_parent); 156 | char *bytes_str(struct object o); 157 | void mark_bytes_obj(struct object o); 158 | void dispose_bytes_obj(struct object o); 159 | 160 | // Error object. 161 | struct object new_error_obj(char *msg, size_t len); 162 | struct object errorf(char *fmt, ...); 163 | char *error_str(struct object o); 164 | void dispose_error_obj(struct object o); 165 | 166 | // List object. 167 | struct object make_list(size_t cap); 168 | struct object new_list_obj(struct object *list, size_t len); 169 | struct object new_list_obj_data(struct object *list, size_t len, size_t cap); 170 | struct object new_list_slice(struct object *list, size_t len, uint32_t *m_parent); 171 | char *list_str(struct object o); 172 | void mark_list_obj(struct object l); 173 | void dispose_list_obj(struct object o); 174 | struct list list_copy(struct list l); 175 | 176 | // Pipe object. 177 | struct object new_pipe(); 178 | struct object new_buffered_pipe(size_t size); 179 | int pipe_send(struct object pipe, struct object o); 180 | struct object pipe_recv(struct object pipe); 181 | int pipe_close(struct object pipe); 182 | void mark_pipe_obj(struct object pipe); 183 | void dispose_pipe_obj(struct object pipe); 184 | 185 | // Map object. 186 | struct object new_map(); 187 | struct map_pair map_get(struct object map, struct object k); 188 | struct map_pair map_set(struct object map, struct object k, struct object v); 189 | void mark_map_obj(struct object m); 190 | char *map_str(struct object map); 191 | void map_delete(struct object map, struct object key); 192 | void dispose_map_obj(struct object map); 193 | struct object map_keys(struct object map); 194 | 195 | // Object object. 196 | struct object new_object(); 197 | struct object object_get(struct object obj, char *name); 198 | struct object object_set(struct object obj, char *name, struct object val); 199 | struct object object_to_module(struct object o); 200 | void mark_object_obj(struct object o); 201 | char *object_obj_str(struct object obj); 202 | void dispose_object_obj(struct object obj); 203 | 204 | // Function object. 205 | struct function *new_function(uint8_t *insts, size_t len, uint32_t num_locals, uint32_t num_params, struct bookmark *bmarks, uint32_t num_bookmarks); 206 | struct object new_function_obj(uint8_t *insts, size_t len, uint32_t num_locals, uint32_t num_params, struct bookmark *bmarks, uint32_t num_bookmarks); 207 | char *function_str(struct object o); 208 | void dispose_function_obj(struct object o); 209 | void dispose_function_data(struct function *fn); 210 | 211 | // Closure object. 212 | struct object new_closure_obj(struct function *fn, struct object *free, size_t num_free); 213 | char *closure_str(struct object o); 214 | void dispose_closure_obj(struct object o); 215 | void mark_closure_obj(struct object c); 216 | 217 | // Builtin object. 218 | typedef struct object (*builtin)(struct object *args, size_t len); 219 | extern const builtin builtins[NUM_BUILTINS]; 220 | struct object new_builtin_obj(struct object (*builtin)(struct object *args, size_t len)); 221 | 222 | // Util functions. 223 | char *otype_str(enum obj_type t); 224 | char *object_str(struct object o); 225 | void print_obj(struct object o); 226 | void mark_obj(struct object o); 227 | void free_obj(struct object o); 228 | uint64_t fnv64a(char *s); 229 | uint32_t is_truthy(struct object * restrict o); 230 | -------------------------------------------------------------------------------- /internal/obj/pipe.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "../vm/thrd.h" 4 | #include "object.h" 5 | 6 | int pipe_close(struct object pipe) { 7 | struct pipe *p = pipe.data.pipe; 8 | if (p->is_closed) { 9 | return 0; 10 | } 11 | 12 | mtx_lock(&p->mu); 13 | // Set the flag to indicate that the pipe is closed. 14 | p->is_closed = 1; 15 | // Unblock all threads waiting on not_empty. 16 | cnd_broadcast(&p->not_empty); 17 | mtx_unlock(&p->mu); 18 | 19 | free(p->buf); 20 | mtx_destroy(&pipe.data.pipe->mu); 21 | cnd_destroy(&pipe.data.pipe->not_empty); 22 | cnd_destroy(&pipe.data.pipe->not_full); 23 | return 1; 24 | } 25 | 26 | void dispose_pipe_obj(struct object pipe) { 27 | pipe_close(pipe); 28 | free(pipe.data.pipe); 29 | } 30 | 31 | void mark_pipe_obj(struct object pipe) { 32 | struct pipe *p = pipe.data.pipe; 33 | 34 | for (uint32_t i = 0; i < p->len; i++) { 35 | mark_obj(p->buf[i]); 36 | } 37 | *pipe.marked = 1; 38 | } 39 | 40 | int pipe_send(struct object pipe, struct object o) { 41 | struct pipe *p = pipe.data.pipe; 42 | if (p->is_closed) { 43 | return 0; 44 | } 45 | 46 | mtx_lock(&p->mu); 47 | if (p->is_buffered) { 48 | while (p->len == p->cap) { 49 | cnd_wait(&p->not_full, &p->mu); 50 | } 51 | } else { 52 | if (p->len == p->cap) { 53 | p->cap *= 2; 54 | p->buf = realloc(p->buf, p->cap * sizeof(struct object)); 55 | } 56 | } 57 | p->buf[p->tail] = o; 58 | p->tail = (p->tail + 1) % p->cap; 59 | p->len++; 60 | cnd_signal(&p->not_empty); 61 | mtx_unlock(&p->mu); 62 | return 1; 63 | } 64 | 65 | struct object pipe_recv(struct object pipe) { 66 | struct pipe *p = pipe.data.pipe; 67 | 68 | mtx_lock(&p->mu); 69 | while (p->len == 0 && !p->is_closed) { 70 | cnd_wait(&p->not_empty, &p->mu); 71 | } 72 | 73 | if (p->is_closed) { 74 | mtx_unlock(&p->mu); 75 | return null_obj; 76 | } 77 | 78 | struct object val = p->buf[p->head]; 79 | p->head = (p->head + 1) % p->cap; 80 | p->len--; 81 | cnd_signal(&p->not_full); 82 | mtx_unlock(&p->mu); 83 | 84 | return val; 85 | } 86 | 87 | struct object new_pipe() { 88 | struct pipe *pipe = malloc(sizeof(struct pipe)); 89 | pipe->buf = calloc(1, sizeof(struct object)); 90 | pipe->cap = 1; 91 | pipe->len = 0; 92 | pipe->head = 0; 93 | pipe->tail = 0; 94 | pipe->is_buffered = 0; 95 | mtx_init(&pipe->mu, mtx_plain); 96 | cnd_init(&pipe->not_empty); 97 | cnd_init(&pipe->not_full); 98 | 99 | return (struct object) { 100 | .data.pipe = pipe, 101 | .type = obj_pipe, 102 | .marked = MARKPTR() 103 | }; 104 | } 105 | 106 | struct object new_buffered_pipe(size_t size) { 107 | struct pipe *pipe = malloc(sizeof(struct pipe)); 108 | pipe->buf = calloc(size, sizeof(struct object)); 109 | pipe->cap = size; 110 | pipe->len = 0; 111 | pipe->head = 0; 112 | pipe->tail = 0; 113 | pipe->is_buffered = 1; 114 | mtx_init(&pipe->mu, mtx_plain); 115 | cnd_init(&pipe->not_empty); 116 | cnd_init(&pipe->not_full); 117 | 118 | return (struct object) { 119 | .data.pipe = pipe, 120 | .type = obj_pipe, 121 | .marked = MARKPTR() 122 | }; 123 | } 124 | -------------------------------------------------------------------------------- /internal/obj/plugin.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #if !defined(_WIN32) && !defined(WIN32) 4 | #include 5 | #else 6 | #include 7 | 8 | #define RTLD_LAZY NULL 9 | #define dlopen(path, mode) LoadLibrary((path)) 10 | #define dlclose(handle) FreeLibrary((HMODULE)(handle)) 11 | #define dlsym(handle, name) GetProcAddress((handle), (name)) 12 | 13 | inline char *dlerror() { 14 | DWORD dwError = GetLastError(); 15 | char* lpMsgBuf = NULL; 16 | 17 | if (dwError != 0) { 18 | FormatMessage( 19 | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 20 | NULL, 21 | dwError, 22 | MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 23 | (LPTSTR) &lpMsgBuf, 24 | 0, 25 | NULL 26 | ); 27 | } 28 | return lpMsgBuf; 29 | } 30 | #endif 31 | -------------------------------------------------------------------------------- /internal/obj/string.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "object.h" 4 | 5 | #if defined(_WIN32) || defined(WIN32) 6 | char *strndup(char * restrict s, size_t len) { 7 | char *dup = malloc(sizeof(char) * len + 1); 8 | dup[len] = '\0'; 9 | memcpy(dup, s, sizeof(char) * len); 10 | 11 | return dup; 12 | } 13 | #endif 14 | 15 | void dispose_string_obj(struct object o) { 16 | // Free everything if it's not a slice (marked parent bit is set to NULL). 17 | if (o.data.str->m_parent == NULL) { 18 | free(o.marked); 19 | free(o.data.str->str); 20 | } 21 | free(o.data.str); 22 | } 23 | 24 | char *string_str(struct object o) { 25 | return strndup(o.data.str->str, o.data.str->len); 26 | } 27 | 28 | struct object new_string_obj(char *str, size_t len) { 29 | struct string *s = malloc(sizeof(struct string)); 30 | s->str = str; 31 | s->len = len; 32 | s->m_parent = NULL; 33 | 34 | return (struct object) { 35 | .data.str = s, 36 | .type = obj_string, 37 | .marked = MARKPTR(), 38 | }; 39 | } 40 | 41 | void mark_string_obj(struct object s) { 42 | *s.marked = 1; 43 | if (s.data.str->m_parent != NULL) { 44 | *s.data.str->m_parent = 1; 45 | } 46 | } 47 | 48 | struct object new_string_slice(char *str, size_t len, uint32_t *m_parent) { 49 | struct string *s = malloc(sizeof(struct string)); 50 | s->str = str; 51 | s->len = len; 52 | s->m_parent = m_parent; 53 | 54 | return (struct object) { 55 | .data.str = s, 56 | .type = obj_string, 57 | .marked = MARKPTR(), 58 | }; 59 | } 60 | -------------------------------------------------------------------------------- /internal/obj/utils.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "object.h" 5 | #include "plugin.h" 6 | 7 | char *otype_str(enum obj_type t) { 8 | static char *strings[] = { 9 | "null", 10 | "bool", 11 | "int", 12 | "float", 13 | "builtin", 14 | "string", 15 | "error", 16 | "list", 17 | "map", 18 | "function", 19 | "closure", 20 | "object", 21 | "pipe", 22 | "bytes", 23 | "native" 24 | }; 25 | return t <= obj_native ? strings[t] : "corrupted"; 26 | } 27 | 28 | char *object_str(struct object o) { 29 | switch (o.type) { 30 | case obj_null: 31 | return strdup("null"); 32 | case obj_boolean: 33 | return boolean_str(o); 34 | case obj_integer: 35 | return integer_str(o); 36 | case obj_float: 37 | return float_str(o); 38 | case obj_builtin: 39 | return strdup(""); 40 | case obj_string: 41 | return string_str(o); 42 | case obj_error: 43 | return error_str(o); 44 | case obj_list: 45 | return list_str(o); 46 | case obj_map: 47 | return map_str(o); 48 | case obj_function: 49 | return function_str(o); 50 | case obj_closure: 51 | return closure_str(o); 52 | case obj_object: 53 | return object_obj_str(o); 54 | case obj_pipe: 55 | return strdup(""); 56 | case obj_bytes: 57 | return bytes_str(o); 58 | case obj_native: 59 | return strdup(""); 60 | default: 61 | return strdup(""); 62 | } 63 | } 64 | 65 | void print_obj(struct object o) { 66 | char *str = object_str(o); 67 | puts(str); 68 | free(str); 69 | } 70 | 71 | inline void mark_obj(struct object o) { 72 | if (o.type > obj_builtin) { 73 | switch (o.type) { 74 | case obj_object: 75 | mark_object_obj(o); 76 | break; 77 | case obj_list: 78 | mark_list_obj(o); 79 | break; 80 | case obj_closure: 81 | mark_closure_obj(o); 82 | break; 83 | case obj_map: 84 | mark_map_obj(o); 85 | break; 86 | case obj_string: 87 | mark_string_obj(o); 88 | break; 89 | case obj_bytes: 90 | mark_bytes_obj(o); 91 | break; 92 | case obj_pipe: 93 | mark_pipe_obj(o); 94 | break; 95 | default: 96 | *o.marked = 1; 97 | break; 98 | } 99 | } 100 | } 101 | 102 | void free_obj(struct object o) { 103 | switch (o.type) { 104 | case obj_string: 105 | dispose_string_obj(o); 106 | return; 107 | case obj_error: 108 | dispose_error_obj(o); 109 | return; 110 | case obj_list: 111 | dispose_list_obj(o); 112 | return; 113 | case obj_map: 114 | dispose_map_obj(o); 115 | return; 116 | case obj_function: 117 | dispose_function_obj(o); 118 | return; 119 | case obj_closure: 120 | dispose_closure_obj(o); 121 | return; 122 | case obj_object: 123 | dispose_object_obj(o); 124 | return; 125 | case obj_pipe: 126 | dispose_pipe_obj(o); 127 | return; 128 | case obj_bytes: 129 | dispose_bytes_obj(o); 130 | return; 131 | case obj_native: 132 | free(o.marked); 133 | dlclose(o.data.handle); 134 | return; 135 | default: 136 | return; 137 | } 138 | } 139 | 140 | inline uint32_t is_truthy(struct object * o) { 141 | switch (o->type) { 142 | case obj_boolean: 143 | return o->data.i == 1; 144 | case obj_integer: 145 | return o->data.i != 0; 146 | case obj_float: 147 | return o->data.f != 0; 148 | case obj_string: 149 | return o->data.str->len != 0; 150 | case obj_null: 151 | return 0; 152 | default: 153 | return 1; 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /internal/tauerr/bookmark.go: -------------------------------------------------------------------------------- 1 | package tauerr 2 | 3 | // #include "bookmark.h" 4 | import "C" 5 | 6 | type Bookmark = C.struct_bookmark 7 | 8 | func NewBookmark(fileCnt string, filePos, offset int) Bookmark { 9 | line, lineNo, relative := line(fileCnt, filePos) 10 | 11 | return Bookmark{ 12 | offset: C.int32_t(offset), 13 | lineno: C.int32_t(lineNo), 14 | pos: C.int32_t(relative), 15 | len: C.size_t(len(line)), 16 | line: C.CString(line), 17 | } 18 | } 19 | 20 | func NewRawBookmark(line string, offset, lineNo, pos int) Bookmark { 21 | return Bookmark{ 22 | offset: C.int32_t(offset), 23 | lineno: C.int32_t(lineNo), 24 | pos: C.int32_t(pos), 25 | len: C.size_t(len(line)), 26 | line: C.CString(line), 27 | } 28 | } 29 | 30 | func (b Bookmark) Offset() int { 31 | return int(b.offset) 32 | } 33 | 34 | func (b Bookmark) LineNo() int { 35 | return int(b.lineno) 36 | } 37 | 38 | func (b Bookmark) Pos() int { 39 | return int(b.pos) 40 | } 41 | 42 | func (b Bookmark) Len() int { 43 | return int(b.len) 44 | } 45 | 46 | func (b Bookmark) Line() string { 47 | return C.GoString(b.line) 48 | } 49 | -------------------------------------------------------------------------------- /internal/tauerr/bookmark.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | struct bookmark { 6 | int32_t offset; 7 | int32_t lineno; 8 | int32_t pos; 9 | size_t len; 10 | char *line; 11 | }; 12 | -------------------------------------------------------------------------------- /internal/tauerr/tauerr.go: -------------------------------------------------------------------------------- 1 | package tauerr 2 | 3 | import "C" 4 | 5 | import ( 6 | "fmt" 7 | "strings" 8 | ) 9 | 10 | func New(file, input string, pos int, s string, a ...any) error { 11 | if file == "" { 12 | file = "" 13 | } 14 | 15 | line, lineno, rel := line(input, pos) 16 | return fmt.Errorf( 17 | "error in file %s at line %d:\n %s\n %s\n%s", 18 | file, 19 | lineno, 20 | line, 21 | arrow(rel), 22 | fmt.Sprintf(s, a...), 23 | ) 24 | } 25 | 26 | func NewFromBookmark(file string, b Bookmark, s string, a ...any) error { 27 | if b == (Bookmark{}) { 28 | return fmt.Errorf(s, a...) 29 | } 30 | 31 | return fmt.Errorf( 32 | "error in file %s at line %d:\n %s\n %s\n%s", 33 | file, 34 | int(b.lineno), 35 | C.GoString(b.line), 36 | arrow(int(b.pos)), 37 | fmt.Sprintf(s, a...), 38 | ) 39 | } 40 | 41 | func line(input string, pos int) (line string, lineno, relative int) { 42 | s, e := start(input, pos), end(input, int(pos)) 43 | l := input[s:e] 44 | line = strings.TrimLeft(l, " \t") 45 | return line, lineNo(input, pos), len(line) - (e - pos) 46 | } 47 | 48 | func start(s string, pos int) int { 49 | for i := pos - 1; i >= 0; i-- { 50 | if s[i] == '\n' { 51 | return i + 1 52 | } 53 | } 54 | return 0 55 | } 56 | 57 | func end(s string, pos int) int { 58 | for i := pos; i < len(s); i++ { 59 | if s[i] == '\n' { 60 | return i 61 | } 62 | } 63 | return len(s) 64 | } 65 | 66 | func lineNo(s string, pos int) int { 67 | var cnt = 1 68 | 69 | for _, b := range s[:pos] { 70 | if b == '\n' { 71 | cnt++ 72 | } 73 | } 74 | 75 | return cnt 76 | } 77 | 78 | func arrow(pos int) string { 79 | var s = make([]byte, pos+1) 80 | 81 | for i := range s { 82 | if i == pos { 83 | s[i] = '^' 84 | } else { 85 | s[i] = ' ' 86 | } 87 | } 88 | return string(s) 89 | } 90 | -------------------------------------------------------------------------------- /internal/vm/heap.c: -------------------------------------------------------------------------------- 1 | #include "vm.h" 2 | 3 | inline struct heap new_heap(int64_t treshold) { 4 | return (struct heap) { 5 | .root = NULL, 6 | .len = 0, 7 | .treshold = treshold, 8 | }; 9 | } 10 | 11 | inline void heap_add(struct heap *h, struct object obj) { 12 | struct heap_node *node = malloc(sizeof(struct heap_node)); 13 | node->next = h->root; 14 | node->obj = obj; 15 | 16 | h->root = node; 17 | h->treshold *= (++h->len >= h->treshold) + 1; 18 | } 19 | 20 | inline void heap_dispose(struct heap *h) { 21 | for (struct heap_node *n = h->root; n != NULL;) { 22 | struct heap_node *tmp = n->next; 23 | free_obj(n->obj); 24 | free(n); 25 | n = tmp; 26 | } 27 | h->root = NULL; 28 | h->len = 0; 29 | h->treshold = 1024; 30 | } 31 | -------------------------------------------------------------------------------- /internal/vm/jump_table.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | static const void *jump_table[] = { 4 | &&TARGET_HALT, 5 | &&TARGET_POP, 6 | 7 | &&TARGET_CONST, 8 | &&TARGET_TRUE, 9 | &&TARGET_FALSE, 10 | &&TARGET_NULL, 11 | &&TARGET_LIST, 12 | &&TARGET_MAP, 13 | &&TARGET_CLOSURE, 14 | &&TARGET_CURRENT_CLOSURE, 15 | 16 | &&TARGET_ADD, 17 | &&TARGET_SUB, 18 | &&TARGET_MUL, 19 | &&TARGET_DIV, 20 | &&TARGET_MOD, 21 | 22 | &&TARGET_BW_AND, 23 | &&TARGET_BW_OR, 24 | &&TARGET_BW_XOR, 25 | &&TARGET_BW_NOT, 26 | &&TARGET_BW_LSHIFT, 27 | &&TARGET_BW_RSHIFT, 28 | 29 | &&TARGET_AND, 30 | &&TARGET_OR, 31 | &&TARGET_EQUAL, 32 | &&TARGET_NOT_EQUAL, 33 | &&TARGET_GREATER_THAN, 34 | &&TARGET_GREATER_THAN_EQUAL, 35 | 36 | &&TARGET_MINUS, 37 | &&TARGET_BANG, 38 | &&TARGET_INDEX, 39 | 40 | &&TARGET_CALL, 41 | &&TARGET_CONCURRENT_CALL, 42 | &&TARGET_RETURN, 43 | &&TARGET_RETURN_VALUE, 44 | 45 | &&TARGET_JUMP, 46 | &&TARGET_JUMP_NOT_TRUTHY, 47 | 48 | &&TARGET_DOT, 49 | &&TARGET_DEFINE, 50 | &&TARGET_GET_GLOBAL, 51 | &&TARGET_SET_GLOBAL, 52 | &&TARGET_GET_LOCAL, 53 | &&TARGET_SET_LOCAL, 54 | &&TARGET_GET_BUILTIN, 55 | &&TARGET_GET_FREE, 56 | &&TARGET_LOAD_MODULE, 57 | &&TARGET_INTERPOLATE, 58 | }; 59 | -------------------------------------------------------------------------------- /internal/vm/opcode.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | enum opcode { 4 | op_halt, 5 | op_pop, 6 | 7 | op_constant, 8 | op_true, 9 | op_false, 10 | op_null, 11 | op_list, 12 | op_map, 13 | op_closure, 14 | op_current_closure, 15 | 16 | op_add, 17 | op_sub, 18 | op_mul, 19 | op_div, 20 | op_mod, 21 | 22 | op_bw_and, 23 | op_bw_or, 24 | op_bw_xor, 25 | op_bw_not, 26 | op_bw_lshift, 27 | op_bw_rshift, 28 | 29 | op_and, 30 | op_or, 31 | op_equal, 32 | op_not_equal, 33 | op_greater_than, 34 | op_greater_than_equal, 35 | 36 | op_minus, 37 | op_bang, 38 | op_index, 39 | 40 | op_call, 41 | op_concurrent_call, 42 | op_return, 43 | op_return_value, 44 | 45 | op_jump, 46 | op_jump_not_truthy, 47 | 48 | op_dot, 49 | op_define, 50 | op_get_global, 51 | op_set_global, 52 | op_get_local, 53 | op_set_local, 54 | op_get_builtin, 55 | op_get_free, 56 | op_load_module, 57 | op_interpolate 58 | }; 59 | 60 | char *opcode_str(enum opcode op) { 61 | static char *strings[] = { 62 | "op_halt", 63 | "op_pop", 64 | 65 | "op_constant", 66 | "op_true", 67 | "op_false", 68 | "op_null", 69 | "op_list", 70 | "op_map", 71 | "op_closure", 72 | "op_current_closure", 73 | 74 | "op_add", 75 | "op_sub", 76 | "op_mul", 77 | "op_div", 78 | "op_mod", 79 | 80 | "op_bw_and", 81 | "op_bw_or", 82 | "op_bw_xor", 83 | "op_bw_not", 84 | "op_bw_lshift", 85 | "op_bw_rshift", 86 | 87 | "op_and", 88 | "op_or", 89 | "op_equal", 90 | "op_not_equal", 91 | "op_greater_than", 92 | "op_greater_than_equal", 93 | 94 | "op_minus", 95 | "op_bang", 96 | "op_index", 97 | 98 | "op_call", 99 | "op_concurrent_call", 100 | "op_return", 101 | "op_return_value", 102 | 103 | "op_jump", 104 | "op_jump_not_truthy", 105 | 106 | "op_dot", 107 | "op_define", 108 | "op_get_global", 109 | "op_set_global", 110 | "op_get_local", 111 | "op_set_local", 112 | "op_get_builtin", 113 | "op_get_free", 114 | "op_load_module", 115 | "op_interpolate", 116 | }; 117 | 118 | return strings[op]; 119 | } 120 | -------------------------------------------------------------------------------- /internal/vm/pool.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "vm.h" 5 | 6 | inline struct pool *new_pool(size_t cap) { 7 | struct pool *p = malloc(sizeof(struct pool)); 8 | p->list = calloc(cap, sizeof(struct object)); 9 | p->cap = cap; 10 | p->len = 0; 11 | 12 | return p; 13 | } 14 | 15 | inline struct pool *poolcpy(struct pool *p) { 16 | struct pool *ret = malloc(sizeof(struct pool)); 17 | ret->list = malloc(sizeof(struct object) * p->cap); 18 | ret->cap = p->cap; 19 | ret->len = p->len; 20 | memcpy(ret->list, p->list, sizeof(struct object) * p->cap); 21 | 22 | return ret; 23 | } 24 | 25 | inline void pool_append(struct pool *p, struct object o) { 26 | if (p->len == p->cap) { 27 | p->cap = p->cap > 0 ? p->cap * 2 : 1; 28 | p->list = realloc(p->list, p->cap * sizeof(struct object)); 29 | } 30 | p->list[p->len++] = o; 31 | } 32 | 33 | inline void pool_insert(struct pool *p, size_t idx, struct object o) { 34 | if (idx >= p->cap) { 35 | p->cap = p->cap > 0 ? pow(2, ceil(log2(idx + 1))) : 1; 36 | p->list = realloc(p->list, p->cap * sizeof(struct object)); 37 | } 38 | p->list[idx] = o; 39 | if (idx >= p->len) p->len = idx + 1; 40 | } 41 | 42 | inline void pool_dispose(struct pool *p) { 43 | free(p->list); 44 | free(p); 45 | } 46 | -------------------------------------------------------------------------------- /internal/vm/thrd.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #if __has_include() 4 | #include 5 | #elif __has_include() 6 | #include 7 | 8 | #ifdef __clang__ 9 | struct warg { 10 | int (*fn)(void *); 11 | void *arg; 12 | }; 13 | 14 | static void *wrapper(void *arg) { 15 | struct warg *a = arg; 16 | a->fn(a->arg); 17 | return NULL; 18 | } 19 | #endif 20 | 21 | // Thread 22 | #define thrd_t pthread_t 23 | #define thrd_success 0 24 | #ifdef __clang__ 25 | #define thrd_create(thrd, _fn, _arg) ({ \ 26 | struct warg a = (struct warg) {.fn = (_fn), .arg = (_arg)}; \ 27 | pthread_create((thrd), NULL, wrapper, &a); \ 28 | }) 29 | #else 30 | #define thrd_create(thrd, fn, arg) ({ \ 31 | void *wrapper(void *a) { return (void *)(intptr_t)fn(a); } \ 32 | pthread_create((thrd), NULL, wrapper, (arg)); \ 33 | }) 34 | #endif 35 | 36 | // Mutex 37 | #define mtx_t pthread_mutex_t 38 | #define mtx_plain NULL 39 | #define mtx_init pthread_mutex_init 40 | #define mtx_lock pthread_mutex_lock 41 | #define mtx_unlock pthread_mutex_unlock 42 | #define mtx_destroy pthread_mutex_destroy 43 | 44 | // Condition 45 | #define cnd_t pthread_cond_t 46 | #define cnd_init(arg) pthread_cond_init((arg), NULL) 47 | #define cnd_broadcast pthread_cond_broadcast 48 | #define cnd_signal pthread_cond_signal 49 | #define cnd_wait pthread_cond_wait 50 | #define cnd_destroy pthread_cond_destroy 51 | #elif defined(_WIN32) || defined(WIN32) 52 | #include 53 | #include 54 | 55 | // Thread 56 | #define thrd_t HANDLE 57 | #define thrd_success 0 58 | #define thrd_create(thrd, fn, arg) ((*(thrd) = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)(fn), (arg), 0, NULL)) == NULL) 59 | 60 | // Mutex 61 | #define mtx_t CRITICAL_SECTION 62 | #define mtx_plain NULL 63 | #define mtx_init(cs, mode) InitializeCriticalSection((cs)) 64 | #define mtx_lock EnterCriticalSection 65 | #define mtx_unlock LeaveCriticalSection 66 | #define mtx_destroy DeleteCriticalSection 67 | 68 | // Condition 69 | #define cnd_t CONDITION_VARIABLE 70 | #define cnd_init(arg) InitializeConditionVariable(arg) 71 | #define cnd_broadcast WakeAllConditionVariable 72 | #define cnd_signal WakeConditionVariable 73 | #define cnd_wait(cond, mtx) while (!SleepConditionVariableCS(cond, mtx, INFINITE)) {} 74 | #define cnd_destroy(cnd) 75 | #else 76 | #error "unsupported threading library" 77 | #endif 78 | -------------------------------------------------------------------------------- /internal/vm/vm.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | /* 4 | #cgo CFLAGS: -g -Ofast -fopenmp -I../obj/libffi/include 5 | #cgo LDFLAGS: -fopenmp -L../obj/libffi/lib -lm 6 | #include 7 | #include 8 | #include "vm.h" 9 | #include "../obj/object.h" 10 | #include "../compiler/bytecode.h" 11 | 12 | static inline struct object get_global(struct pool *globals, size_t idx) { 13 | return globals->list[idx]; 14 | } 15 | 16 | static inline void set_const(struct object *list, size_t idx, struct object o) { 17 | list[idx] = o; 18 | } 19 | */ 20 | import "C" 21 | import ( 22 | "fmt" 23 | "os" 24 | "path/filepath" 25 | "unicode" 26 | "unicode/utf8" 27 | "unsafe" 28 | 29 | "github.com/NicoNex/tau/internal/compiler" 30 | "github.com/NicoNex/tau/internal/obj" 31 | "github.com/NicoNex/tau/internal/parser" 32 | "golang.org/x/term" 33 | ) 34 | 35 | type ( 36 | VM = *C.struct_vm 37 | State = C.struct_state 38 | Bookmark = C.struct_bookmark 39 | ) 40 | 41 | var ( 42 | Consts []obj.Object 43 | importTab = make(map[string]C.struct_object) 44 | TermState *term.State 45 | ) 46 | 47 | func NewState() State { 48 | return C.new_state() 49 | } 50 | 51 | func (s State) Free() { 52 | C.state_dispose(s) 53 | } 54 | 55 | func (s *State) SetConsts(consts []obj.Object) { 56 | s.consts.list = (*C.struct_object)(unsafe.Pointer(&consts[0])) 57 | s.consts.len = C.size_t(len(consts)) 58 | s.consts.cap = C.size_t(len(consts)) 59 | } 60 | 61 | func (s State) NumDefs() int { 62 | return int(s.ndefs) 63 | } 64 | 65 | func New(file string, bc compiler.Bytecode) VM { 66 | Consts = bc.Consts() 67 | return C.new_vm(C.CString(file), cbytecode(bc)) 68 | } 69 | 70 | func NewWithState(file string, bc compiler.Bytecode, state State) VM { 71 | Consts = bc.Consts() 72 | if len(Consts) > 0 { 73 | state.SetConsts(Consts) 74 | } 75 | return C.new_vm_with_state(C.CString(file), cbytecode(bc), state) 76 | } 77 | 78 | func (vm VM) Run() { 79 | C.vm_run(vm) 80 | C.fflush(C.stdout) 81 | } 82 | 83 | func (vm VM) State() State { 84 | return vm.state 85 | } 86 | 87 | func (vm VM) Free() { 88 | C.vm_dispose(vm) 89 | } 90 | 91 | func (vm VM) LastPoppedStackObj() obj.Object { 92 | o := C.vm_last_popped_stack_elem(vm) 93 | return *(*obj.Object)(unsafe.Pointer(&o)) 94 | } 95 | 96 | func cobj(o obj.Object) C.struct_object { 97 | return *(*C.struct_object)(unsafe.Pointer(&o)) 98 | } 99 | 100 | func cbytecode(bc compiler.Bytecode) C.struct_bytecode { 101 | return *(*C.struct_bytecode)(unsafe.Pointer(&bc)) 102 | } 103 | 104 | func isExported(n string) bool { 105 | r, _ := utf8.DecodeRuneInString(n) 106 | return unicode.IsUpper(r) 107 | } 108 | 109 | func lookupPaths(vmpath, taupath string) []string { 110 | home, err := os.UserHomeDir() 111 | taupath = filepath.Clean(taupath) 112 | 113 | // If the module name has no extension. 114 | if filepath.Ext(taupath) != "" { 115 | paths := []string{taupath, filepath.Join(vmpath, taupath)} 116 | 117 | if err == nil { 118 | paths = append( 119 | paths, 120 | filepath.Join(home, ".local", "lib", "tau", taupath), 121 | ) 122 | } 123 | 124 | paths = append(paths, filepath.Join("/", "lib", "tau", taupath)) 125 | return paths 126 | } 127 | 128 | paths := []string{ 129 | taupath + ".tau", 130 | taupath + ".tauc", 131 | filepath.Join(vmpath, taupath) + ".tau", 132 | filepath.Join(vmpath, taupath) + ".tauc", 133 | } 134 | 135 | if err == nil { 136 | paths = append( 137 | paths, 138 | filepath.Join(home, ".local", "lib", "tau", taupath+".tau"), 139 | filepath.Join(home, ".local", "lib", "tau", taupath+".tauc"), 140 | ) 141 | } 142 | 143 | paths = append( 144 | paths, 145 | filepath.Join("/", "lib", "tau", taupath+".tau"), 146 | filepath.Join("/", "lib", "tau", taupath+".tauc"), 147 | ) 148 | 149 | return paths 150 | } 151 | 152 | func lookup(vmfile, taupath string) (string, error) { 153 | for _, p := range lookupPaths(filepath.Base(vmfile), taupath) { 154 | if _, err := os.Stat(p); err == nil { 155 | return p, nil 156 | } 157 | } 158 | return "", fmt.Errorf("no module named %q", taupath) 159 | } 160 | 161 | //export vm_exec_load_module 162 | func vm_exec_load_module(vm *C.struct_vm, cpath *C.char) int { 163 | path := C.GoString(cpath) 164 | 165 | if path == "" { 166 | C.go_vm_errorf(vm, C.CString("import: no file provided")) 167 | return 1 168 | } 169 | 170 | p, err := lookup(C.GoString(vm.file), path) 171 | if err != nil { 172 | msg := fmt.Sprintf("import: %v", err) 173 | C.go_vm_errorf(vm, C.CString(msg)) 174 | return 1 175 | } 176 | 177 | if mod, ok := importTab[p]; ok { 178 | vm.stack[vm.sp] = mod 179 | vm.sp++ 180 | return 1 181 | } 182 | 183 | b, err := os.ReadFile(p) 184 | if err != nil { 185 | msg := fmt.Sprintf("import: %v", err) 186 | C.go_vm_errorf(vm, C.CString(msg)) 187 | return 1 188 | } 189 | 190 | tree, errs := parser.Parse(path, string(b)) 191 | if len(errs) > 0 { 192 | m := fmt.Sprintf("import: multiple errors in module %s", path) 193 | C.go_vm_errorf(vm, C.CString(m)) 194 | return 1 195 | } 196 | 197 | c := compiler.NewImport(int(vm.state.ndefs), &Consts) 198 | c.SetFileInfo(path, string(b)) 199 | if err := c.Compile(tree); err != nil { 200 | C.go_vm_errorf(vm, C.CString(err.Error())) 201 | return 1 202 | } 203 | 204 | bc := c.Bytecode() 205 | (&vm.state).SetConsts(Consts) 206 | vm.state.ndefs = C.uint32_t(bc.NDefs()) 207 | tvm := C.new_vm_with_state(C.CString(path), cbytecode(bc), vm.state) 208 | defer C.vm_dispose(tvm) 209 | if i := C.vm_run(tvm); i != 0 { 210 | C.go_vm_errorf(vm, C.CString("import error")) 211 | return 1 212 | } 213 | vm.state = tvm.state 214 | 215 | mod := C.new_object() 216 | for name, sym := range c.Store { 217 | if sym.Scope == compiler.GlobalScope { 218 | o := C.get_global(vm.state.globals, C.size_t(sym.Index)) 219 | 220 | if isExported(name) { 221 | if o._type == C.obj_object { 222 | C.object_set(mod, C.CString(name), C.object_to_module(o)) 223 | } else { 224 | C.object_set(mod, C.CString(name), o) 225 | } 226 | } 227 | } 228 | } 229 | 230 | importTab[p] = mod 231 | vm.stack[vm.sp] = mod 232 | vm.sp++ 233 | return 0 234 | } 235 | 236 | //export restore_term 237 | func restore_term() { 238 | if TermState != nil { 239 | term.Restore(int(os.Stdin.Fd()), TermState) 240 | } 241 | } 242 | 243 | func init() { 244 | C.set_exit() 245 | } 246 | -------------------------------------------------------------------------------- /internal/vm/vm.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "../obj/object.h" 6 | #include "../compiler/bytecode.h" 7 | 8 | #define STACK_SIZE 2048 9 | #define GLOBAL_SIZE 65536 10 | #define MAX_FRAMES 16384 11 | #define HEAP_TRESHOLD 1024 12 | 13 | struct frame { 14 | struct object cl; 15 | uint8_t *ip; 16 | uint8_t *start; 17 | uint32_t base_ptr; 18 | }; 19 | 20 | struct heap_node { 21 | struct object obj; 22 | struct heap_node* next; 23 | }; 24 | 25 | struct heap { 26 | struct heap_node* root; 27 | size_t len; 28 | int64_t treshold; 29 | }; 30 | 31 | struct pool { 32 | struct object *list; 33 | size_t cap; 34 | size_t len; 35 | }; 36 | 37 | struct state { 38 | struct heap heap; 39 | struct pool *globals; 40 | struct pool consts; 41 | uint32_t ndefs; 42 | }; 43 | 44 | struct vm { 45 | struct state state; 46 | struct object stack[STACK_SIZE]; 47 | struct frame frames[MAX_FRAMES]; 48 | uint32_t sp; 49 | uint32_t frame_idx; 50 | char *file; 51 | jmp_buf env; 52 | }; 53 | 54 | // Pool object. 55 | struct pool *new_pool(size_t cap); 56 | struct pool *poolcpy(struct pool *p); 57 | void pool_append(struct pool *p, struct object o); 58 | void pool_insert(struct pool *p, size_t idx, struct object o); 59 | void pool_dispose(struct pool *p); 60 | 61 | // VM object. 62 | struct state new_state(); 63 | struct vm *new_vm(char *file, struct bytecode bytecode); 64 | struct vm *new_vm_with_state(char *file, struct bytecode bc, struct state state); 65 | int vm_run(struct vm * restrict vm); 66 | void vm_errorf(struct vm * restrict vm, const char *fmt, ...); 67 | void go_vm_errorf(struct vm * restrict vm, const char *fmt); 68 | struct object vm_last_popped_stack_elem(struct vm * restrict vm); 69 | void vm_dispose(struct vm *vm); 70 | void state_dispose(struct state s); 71 | void set_exit(); 72 | 73 | // Heap object. 74 | struct heap new_heap(int64_t treshold); 75 | void heap_add(struct heap *h, struct object obj); 76 | void heap_dispose(struct heap *h); 77 | -------------------------------------------------------------------------------- /profile.go: -------------------------------------------------------------------------------- 1 | //go:build ignore 2 | 3 | package main 4 | 5 | import ( 6 | "os" 7 | "runtime/pprof" 8 | 9 | "github.com/NicoNex/tau/internal/compiler" 10 | "github.com/NicoNex/tau/internal/parser" 11 | "github.com/NicoNex/tau/internal/vm" 12 | 13 | _ "github.com/ianlancetaylor/cgosymbolizer" 14 | ) 15 | 16 | const fib = ` 17 | fib = fn(n) { 18 | if n < 2 { 19 | return n 20 | } 21 | fib(n-1) + fib(n-2) 22 | } 23 | 24 | println(fib(40))` 25 | 26 | func check(err error) { 27 | if err != nil { 28 | panic(err) 29 | } 30 | } 31 | 32 | func code(path string) string { 33 | b, err := os.ReadFile(path) 34 | check(err) 35 | 36 | return string(b) 37 | } 38 | 39 | func fileOrDefault() string { 40 | if len(os.Args) > 1 { 41 | return code(os.Args[1]) 42 | } 43 | return fib 44 | } 45 | 46 | func main() { 47 | cpuf, err := os.Create("cpu.prof") 48 | check(err) 49 | 50 | tauCode := fileOrDefault() 51 | tree, errs := parser.Parse("", tauCode) 52 | if len(errs) > 0 { 53 | panic("parser errors") 54 | } 55 | 56 | c := compiler.New() 57 | c.SetFileInfo("", tauCode) 58 | check(c.Compile(tree)) 59 | 60 | check(pprof.StartCPUProfile(cpuf)) 61 | defer pprof.StopCPUProfile() 62 | tvm := vm.New("", c.Bytecode()) 63 | tvm.Run() 64 | } 65 | -------------------------------------------------------------------------------- /redirect.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | 3 | package tau 4 | 5 | import ( 6 | "fmt" 7 | "io" 8 | "os" 9 | "syscall" 10 | ) 11 | 12 | func redirectStdout(w io.Writer) { 13 | pr, pw, err := os.Pipe() 14 | if err != nil { 15 | fmt.Println("Error creating pipe:", err) 16 | return 17 | } 18 | // Set the pipe writer as the stdout 19 | syscall.Dup2(int(pw.Fd()), syscall.Stdout) 20 | 21 | go func() { 22 | var buf = make([]byte, 4096) 23 | 24 | for { 25 | n, err := pr.Read(buf) 26 | if err != nil { 27 | if err != io.EOF { 28 | fmt.Println("error reading from pipe:", err) 29 | } 30 | break 31 | } 32 | 33 | // Write the captured output to the provided writer 34 | _, err = w.Write(buf[:n]) 35 | if err != nil { 36 | fmt.Println("error writing to writer:", err) 37 | break 38 | } 39 | } 40 | pr.Close() 41 | }() 42 | } 43 | -------------------------------------------------------------------------------- /redirect_win.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | 3 | package tau 4 | 5 | import ( 6 | "fmt" 7 | "io" 8 | "os" 9 | 10 | "golang.org/x/sys/windows" 11 | ) 12 | 13 | func redirectStdout(w io.Writer) { 14 | pr, pw, err := os.Pipe() 15 | if err != nil { 16 | fmt.Println("Error creating pipe:", err) 17 | return 18 | } 19 | // Set the pipe writer as the stdout 20 | var stdHandle windows.Handle 21 | err = windows.DuplicateHandle(windows.CurrentProcess(), windows.Handle(pw.Fd()), windows.CurrentProcess(), &stdHandle, 0, true, windows.DUPLICATE_SAME_ACCESS) 22 | if err != nil { 23 | fmt.Println("Error duplicating handle:", err) 24 | return 25 | } 26 | err = windows.SetStdHandle(windows.STD_OUTPUT_HANDLE, stdHandle) 27 | if err != nil { 28 | fmt.Println("Error setting stdout:", err) 29 | return 30 | } 31 | 32 | go func() { 33 | var buf = make([]byte, 4096) 34 | 35 | for { 36 | n, err := pr.Read(buf) 37 | if err != nil { 38 | if err != io.EOF { 39 | fmt.Println("error reading from pipe:", err) 40 | } 41 | break 42 | } 43 | 44 | // Write the captured output to the provided writer 45 | _, err = w.Write(buf[:n]) 46 | if err != nil { 47 | fmt.Println("error writing to writer:", err) 48 | break 49 | } 50 | } 51 | pr.Close() 52 | }() 53 | } 54 | -------------------------------------------------------------------------------- /repl.go: -------------------------------------------------------------------------------- 1 | package tau 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "io" 7 | "os" 8 | "strings" 9 | 10 | "github.com/NicoNex/tau/internal/compiler" 11 | "github.com/NicoNex/tau/internal/obj" 12 | "github.com/NicoNex/tau/internal/parser" 13 | "github.com/NicoNex/tau/internal/vm" 14 | "golang.org/x/term" 15 | ) 16 | 17 | func REPL() error { 18 | var ( 19 | state = vm.NewState() 20 | symbols = loadBuiltins(compiler.NewSymbolTable()) 21 | ) 22 | 23 | defer state.Free() 24 | initState, err := term.MakeRaw(int(os.Stdin.Fd())) 25 | if err != nil { 26 | fmt.Println(err) 27 | return fmt.Errorf("error opening terminal: %w", err) 28 | } 29 | vm.TermState = initState 30 | defer term.Restore(int(os.Stdin.Fd()), initState) 31 | 32 | t := term.NewTerminal(os.Stdin, ">>> ") 33 | t.AutoCompleteCallback = autoComplete 34 | redirectStdout(t) 35 | PrintVersionInfo(t) 36 | 37 | for { 38 | input, err := t.ReadLine() 39 | check(t, initState, err) 40 | 41 | input = strings.TrimRight(input, " ") 42 | if input == "" { 43 | continue 44 | } else if len(input) > 0 && input[len(input)-1] == '{' { 45 | input, err = acceptUntil(t, input, "\n\n") 46 | check(t, initState, err) 47 | } 48 | 49 | res, errs := parser.Parse("", input) 50 | if len(errs) != 0 { 51 | for _, e := range errs { 52 | fmt.Fprintln(t, e) 53 | } 54 | continue 55 | } 56 | 57 | c := compiler.NewWithState(symbols, &vm.Consts) 58 | c.SetFileInfo("", input) 59 | if err := c.Compile(res); err != nil { 60 | fmt.Fprintln(t, err) 61 | continue 62 | } 63 | 64 | tvm := vm.NewWithState("", c.Bytecode(), state) 65 | tvm.Run() 66 | // TODO: find a way to make this happen seamlessly. 67 | state = tvm.State() 68 | symbols.NumDefs = state.NumDefs() 69 | tvm.Free() 70 | } 71 | } 72 | 73 | func autoComplete(line string, pos int, key rune) (newLine string, newPos int, ok bool) { 74 | if key == '\t' { 75 | return line + " ", pos + 4, true 76 | } 77 | return 78 | } 79 | 80 | func check(t *term.Terminal, initState *term.State, err error) { 81 | if err != nil { 82 | // Quit without error on Ctrl^D. 83 | if err != io.EOF { 84 | fmt.Fprintln(t, err) 85 | } 86 | term.Restore(0, initState) 87 | fmt.Println() 88 | os.Exit(0) 89 | } 90 | } 91 | 92 | func acceptUntil(t *term.Terminal, start, end string) (string, error) { 93 | var buf strings.Builder 94 | 95 | buf.WriteString(start) 96 | buf.WriteRune('\n') 97 | t.SetPrompt("... ") 98 | defer t.SetPrompt(">>> ") 99 | 100 | for { 101 | line, err := t.ReadLine() 102 | if err != nil { 103 | return "", err 104 | } 105 | 106 | line = strings.TrimRight(line, " ") 107 | buf.WriteString(line) 108 | buf.WriteRune('\n') 109 | 110 | if s := buf.String(); len(s) > len(end) && s[len(s)-len(end):] == end { 111 | break 112 | } 113 | } 114 | 115 | return buf.String(), nil 116 | } 117 | 118 | func SimpleREPL() { 119 | var ( 120 | state = vm.NewState() 121 | symbols = loadBuiltins(compiler.NewSymbolTable()) 122 | reader = bufio.NewReader(os.Stdin) 123 | ) 124 | 125 | defer state.Free() 126 | PrintVersionInfo(os.Stdout) 127 | for { 128 | fmt.Print(">>> ") 129 | input, err := reader.ReadString('\n') 130 | simpleCheck(err) 131 | 132 | input = strings.TrimRight(input, " \n") 133 | if len(input) > 0 && input[len(input)-1] == '{' { 134 | input, err = simpleAcceptUntil(reader, input, "\n\n") 135 | simpleCheck(err) 136 | } 137 | 138 | res, errs := parser.Parse("", input) 139 | if len(errs) != 0 { 140 | for _, e := range errs { 141 | fmt.Println(e) 142 | } 143 | continue 144 | } 145 | 146 | c := compiler.NewWithState(symbols, &vm.Consts) 147 | c.SetFileInfo("", input) 148 | if err := c.Compile(res); err != nil { 149 | fmt.Println(err) 150 | continue 151 | } 152 | 153 | tvm := vm.NewWithState("", c.Bytecode(), state) 154 | tvm.Run() 155 | // TODO: find a way to make this happen seamlessly. 156 | state = tvm.State() 157 | symbols.NumDefs = state.NumDefs() 158 | tvm.Free() 159 | } 160 | } 161 | 162 | func loadBuiltins(st *compiler.SymbolTable) *compiler.SymbolTable { 163 | for i, name := range obj.Builtins { 164 | st.DefineBuiltin(i, name) 165 | } 166 | return st 167 | } 168 | 169 | func simpleCheck(err error) { 170 | if err != nil { 171 | fmt.Println(err) 172 | os.Exit(0) 173 | } 174 | } 175 | 176 | func simpleAcceptUntil(r *bufio.Reader, start, end string) (string, error) { 177 | var buf strings.Builder 178 | 179 | buf.WriteString(start) 180 | buf.WriteRune('\n') 181 | for { 182 | fmt.Print("... ") 183 | line, err := r.ReadString('\n') 184 | if err != nil { 185 | return "", err 186 | } 187 | 188 | line = strings.TrimRight(line, " \n") 189 | buf.WriteString(line) 190 | buf.WriteRune('\n') 191 | 192 | if s := buf.String(); len(s) > len(end) && s[len(s)-len(end):] == end { 193 | break 194 | } 195 | } 196 | 197 | return buf.String(), nil 198 | } 199 | -------------------------------------------------------------------------------- /stdlib/errno.tau: -------------------------------------------------------------------------------- 1 | EPERM = 1 # Operation not permitted 2 | ENOENT = 2 # No such file or directory 3 | ESRCH = 3 # No such process 4 | EINTR = 4 # Interrupted system call 5 | EIO = 5 # Input/output error 6 | ENXIO = 6 # No such device or address 7 | E2BIG = 7 # Argument list too long 8 | ENOEXEC = 8 # Exec format error 9 | EBADF = 9 # Bad file descriptor 10 | ECHILD = 10 # No child processes 11 | EAGAIN = 11 # Resource temporarily unavailable 12 | ENOMEM = 12 # Cannot allocate memory 13 | EACCES = 13 # Permission denied 14 | EFAULT = 14 # Bad address 15 | ENOTBLK = 15 # Block device required 16 | EBUSY = 16 # Device or resource busy 17 | EEXIST = 17 # File exists 18 | EXDEV = 18 # Invalid cross-device link 19 | ENODEV = 19 # No such device 20 | ENOTDIR = 20 # Not a directory 21 | EISDIR = 21 # Is a directory 22 | EINVAL = 22 # Invalid argument 23 | ENFILE = 23 # Too many open files in system 24 | EMFILE = 24 # Too many open files 25 | ENOTTY = 25 # Inappropriate ioctl for device 26 | ETXTBSY = 26 # Text file busy 27 | EFBIG = 27 # File too large 28 | ENOSPC = 28 # No space left on device 29 | ESPIPE = 29 # Illegal seek 30 | EROFS = 30 # Read-only file system 31 | EMLINK = 31 # Too many links 32 | EPIPE = 32 # Broken pipe 33 | EDOM = 33 # Numerical argument out of domain 34 | ERANGE = 34 # Numerical result out of range 35 | EDEADLK = 35 # Resource deadlock avoided 36 | ENAMETOOLONG = 36 # File name too long 37 | ENOLCK = 37 # No locks available 38 | ENOSYS = 38 # Function not implemented 39 | ENOTEMPTY = 39 # Directory not empty 40 | ELOOP = 40 # Too many levels of symbolic links 41 | EWOULDBLOCK = 11 # Resource temporarily unavailable 42 | ENOMSG = 42 # No message of desired type 43 | EIDRM = 43 # Identifier removed 44 | ECHRNG = 44 # Channel number out of range 45 | EL2NSYNC = 45 # Level 2 not synchronized 46 | EL3HLT = 46 # Level 3 halted 47 | EL3RST = 47 # Level 3 reset 48 | ELNRNG = 48 # Link number out of range 49 | EUNATCH = 49 # Protocol driver not attached 50 | ENOCSI = 50 # No CSI structure available 51 | EL2HLT = 51 # Level 2 halted 52 | EBADE = 52 # Invalid exchange 53 | EBADR = 53 # Invalid request descriptor 54 | EXFULL = 54 # Exchange full 55 | ENOANO = 55 # No anode 56 | EBADRQC = 56 # Invalid request code 57 | EBADSLT = 57 # Invalid slot 58 | EDEADLOCK = 35 # Resource deadlock avoided 59 | EBFONT = 59 # Bad font file format 60 | ENOSTR = 60 # Device not a stream 61 | ENODATA = 61 # No data available 62 | ETIME = 62 # Timer expired 63 | ENOSR = 63 # Out of streams resources 64 | ENONET = 64 # Machine is not on the network 65 | ENOPKG = 65 # Package not installed 66 | EREMOTE = 66 # Object is remote 67 | ENOLINK = 67 # Link has been severed 68 | EADV = 68 # Advertise error 69 | ESRMNT = 69 # Srmount error 70 | ECOMM = 70 # Communication error on send 71 | EPROTO = 71 # Protocol error 72 | EMULTIHOP = 72 # Multihop attempted 73 | EDOTDOT = 73 # RFS specific error 74 | EBADMSG = 74 # Bad message 75 | EOVERFLOW = 75 # Value too large for defined data type 76 | ENOTUNIQ = 76 # Name not unique on network 77 | EBADFD = 77 # File descriptor in bad state 78 | EREMCHG = 78 # Remote address changed 79 | ELIBACC = 79 # Can not access a needed shared library 80 | ELIBBAD = 80 # Accessing a corrupted shared library 81 | ELIBSCN = 81 # .lib section in a.out corrupted 82 | ELIBMAX = 82 # Attempting to link in too many shared libraries 83 | ELIBEXEC = 83 # Cannot exec a shared library directly 84 | EILSEQ = 84 # Invalid or incomplete multibyte or wide character 85 | ERESTART = 85 # Interrupted system call should be restarted 86 | ESTRPIPE = 86 # Streams pipe error 87 | EUSERS = 87 # Too many users 88 | ENOTSOCK = 88 # Socket operation on non-socket 89 | EDESTADDRREQ = 89 # Destination address required 90 | EMSGSIZE = 90 # Message too long 91 | EPROTOTYPE = 91 # Protocol wrong type for socket 92 | ENOPROTOOPT = 92 # Protocol not available 93 | EPROTONOSUPPORT = 93 # Protocol not supported 94 | ESOCKTNOSUPPORT = 94 # Socket type not supported 95 | EOPNOTSUPP = 95 # Operation not supported 96 | EPFNOSUPPORT = 96 # Protocol family not supported 97 | EAFNOSUPPORT = 97 # Address family not supported by protocol 98 | EADDRINUSE = 98 # Address already in use 99 | EADDRNOTAVAIL = 99 # Cannot assign requested address 100 | ENETDOWN = 100 # Network is down 101 | ENETUNREACH = 101 # Network is unreachable 102 | ENETRESET = 102 # Network dropped connection on reset 103 | ECONNABORTED = 103 # Software caused connection abort 104 | ECONNRESET = 104 # Connection reset by peer 105 | ENOBUFS = 105 # No buffer space available 106 | EISCONN = 106 # Transport endpoint is already connected 107 | ENOTCONN = 107 # Transport endpoint is not connected 108 | ESHUTDOWN = 108 # Cannot send after transport endpoint shutdown 109 | ETOOMANYREFS = 109 # Too many references: cannot splice 110 | ETIMEDOUT = 110 # Connection timed out 111 | ECONNREFUSED = 111 # Connection refused 112 | EHOSTDOWN = 112 # Host is down 113 | EHOSTUNREACH = 113 # No route to host 114 | EALREADY = 114 # Operation already in progress 115 | EINPROGRESS = 115 # Operation now in progress 116 | ESTALE = 116 # Stale file handle 117 | EUCLEAN = 117 # Structure needs cleaning 118 | ENOTNAM = 118 # Not a XENIX named type file 119 | ENAVAIL = 119 # No XENIX semaphores available 120 | EISNAM = 120 # Is a named type file 121 | EREMOTEIO = 121 # Remote I/O error 122 | EDQUOT = 122 # Disk quota exceeded 123 | ENOMEDIUM = 123 # No medium found 124 | EMEDIUMTYPE = 124 # Wrong medium type 125 | ECANCELED = 125 # Operation canceled 126 | ENOKEY = 126 # Required key not available 127 | EKEYEXPIRED = 127 # Key has expired 128 | EKEYREVOKED = 128 # Key has been revoked 129 | EKEYREJECTED = 129 # Key was rejected by service 130 | EOWNERDEAD = 130 # Owner died 131 | ENOTRECOVERABLE = 131 # State not recoverable 132 | ERFKILL = 132 # Operation not possible due to RF-kill 133 | EHWPOISON = 133 # Memory page has hardware error 134 | ENOTSUP = 95 # Operation not supported 135 | -------------------------------------------------------------------------------- /stdlib/os.tau: -------------------------------------------------------------------------------- 1 | libc = plugin("/usr/lib/libc.so.6") 2 | 3 | eof = -1 4 | 5 | EOF = fn() { eof } 6 | 7 | Mkdir = fn(path, perm) { int(libc.mkdir(path, perm)) } 8 | 9 | Open = fn(path, perm) { 10 | file = new() 11 | 12 | if failed(file.f = libc.fopen(path, perm)) { 13 | return file.f 14 | } 15 | 16 | file.Close = fn() { libc.fclose(file.f) } 17 | 18 | file.Read = fn() { 19 | buf = [] 20 | 21 | for c = int(libc.fgetc(file.f)); c != eof; c = int(libc.fgetc(file.f)) { 22 | buf = append(buf, c) 23 | } 24 | return bytes(buf) 25 | } 26 | 27 | file.ReadString = fn() { string(file.Read()) } 28 | 29 | file.Write = fn(a) { 30 | if type(a) != "bytes" { 31 | if failed(a = bytes(a)) { 32 | return a 33 | } 34 | } 35 | 36 | for i = 0; i < len(a); ++i { 37 | libc.fputc(a[i], file.f) 38 | } 39 | return i 40 | } 41 | 42 | return file 43 | } 44 | 45 | ReadFile = fn(path) { 46 | if failed(f = Open(path, "r")) { 47 | return f 48 | } 49 | if failed(b = f.Read()) { 50 | return b 51 | } 52 | if failed(err = f.Close()) { 53 | return err 54 | } 55 | return b 56 | } 57 | 58 | ReadFileString = fn(path) { string(ReadFile(path)) } 59 | 60 | WriteFile = fn(path, data) { 61 | if type(data) != "bytes" { 62 | if failed(data = bytes(data)) { 63 | return data 64 | } 65 | } 66 | 67 | if failed(f = Open(path, "w")) { 68 | return f 69 | } 70 | if failed(err = f.Write(data)) { 71 | return err 72 | } 73 | f.Close() 74 | } 75 | 76 | Chmod = fn(path, mode) { int(libc.chmod(path, mode)) == 0 } 77 | Remove = fn(path) { int(libc.remove(path)) == 0 } 78 | Unlink = fn(path) { int(libc.unlink(path)) == 0 } 79 | -------------------------------------------------------------------------------- /stdlib/strings.tau: -------------------------------------------------------------------------------- 1 | spaces = "\t\n\v\f\r " 2 | 3 | Contains = fn(str, sub) { 4 | maxAttempts = len(str) - len(sub) 5 | for i = 0; i <= maxAttempts; ++i { 6 | if sub == slice(str, i, i + len(sub)) { 7 | return true 8 | } 9 | } 10 | 11 | return false 12 | } 13 | 14 | ContainsAny = fn(str, chars) { 15 | for i = 0; i < len(chars); ++i { 16 | if Contains(str, chars[i]) { 17 | return true 18 | } 19 | } 20 | 21 | return false 22 | } 23 | 24 | Count = fn(str, sub) { 25 | total = 0 26 | 27 | maxAttempts = len(str) - len(sub) 28 | for i = 0; i < maxAttempts; ++i { 29 | if sub == slice(str, i, i + len(sub)) { 30 | ++total 31 | } 32 | } 33 | 34 | return total 35 | } 36 | 37 | cutResult = fn(before, after, found) { 38 | res = new() 39 | res.Before = before 40 | res.After = after 41 | res.Found = found 42 | 43 | return res 44 | } 45 | 46 | Cut = fn(str, sub) { 47 | maxAttempts = len(str) - len(sub) 48 | for i = 0; i < maxAttempts; ++i { 49 | if sub == slice(str, i, i + len(sub)) { 50 | return cutResult( 51 | slice(str, 0, i), 52 | slice(str, i + len(sub), len(str)), 53 | true 54 | ) 55 | } 56 | } 57 | 58 | return cutResult(str, "", false) 59 | } 60 | 61 | HasPrefix = fn(str, pre) { 62 | if len(pre) > len(str) { 63 | return false 64 | } 65 | 66 | return slice(str, 0, len(pre)) == pre 67 | } 68 | 69 | HasSuffix = fn(str, sub) { 70 | if len(sub) > len(str) { 71 | return false 72 | } 73 | 74 | return slice(str, len(str) - len(sub), len(str)) == sub 75 | } 76 | 77 | Index = fn(str, sub) { 78 | maxAttempts = len(str) - len(sub) 79 | for i = 0; i <= maxAttempts; ++i { 80 | if sub == slice(str, i, i + len(sub)) { 81 | return i 82 | } 83 | } 84 | 85 | return -1 86 | } 87 | 88 | IndexAny = fn(str, chars) { 89 | index = -1 90 | for i = 0; i < len(chars); ++i { 91 | tmp = Index(str, chars[i]) 92 | if tmp != -1 && index > tmp || index == -1 { 93 | index = tmp 94 | } 95 | } 96 | 97 | return index 98 | } 99 | 100 | Join = fn(arr, sep) { 101 | str = "" 102 | for i = 0; i < len(arr); ++i { 103 | if type(arr[i]) != "string" { 104 | return error("array element at index {i} is not a string") 105 | } 106 | 107 | str += if i < len(arr) - 1 { arr[i] + sep } else { arr[i] } 108 | } 109 | 110 | return str 111 | } 112 | 113 | LastIndex = fn(str, sub) { 114 | for i = len(str); i >= 0; --i { 115 | if sub == slice(str, i - len(sub), i) { 116 | return i - len(sub) 117 | } 118 | } 119 | 120 | return -1 121 | } 122 | 123 | LastIndexAny = fn(str, chars) { 124 | index = -1 125 | for i = 0; i < len(chars); ++i { 126 | tmp = LastIndex(str, chars[i]) 127 | if tmp != -1 && tmp > index || index == -1 { 128 | index = tmp 129 | } 130 | } 131 | 132 | return index 133 | } 134 | 135 | Repeat = fn(str, n) { 136 | ret = "" 137 | for i = 0; i < n; ++i { 138 | ret += str 139 | } 140 | 141 | return ret 142 | } 143 | 144 | Reverse = fn(str) { 145 | ret = "" 146 | for i = len(str) - 1; i >= 0; --i { 147 | ret += str[i] 148 | } 149 | 150 | return ret 151 | } 152 | 153 | SplitAfterN = fn(str, sep, n) { 154 | if sep == "" { 155 | return error("splitAfter: empty separator") 156 | } 157 | 158 | ret = [] 159 | for (idx = Index(str, sep)) != -1 && n != 0 { 160 | ret = append(ret, slice(str, 0, idx + len(sep))) 161 | str = slice(str, idx + len(sep), len(str)) 162 | --n 163 | } 164 | 165 | return append(ret, str) 166 | } 167 | 168 | SplitAfter = fn(str, sep) { SplitAfterN(str, sep, -1) } 169 | 170 | SplitN = fn(str, sep, n) { 171 | if sep == "" { 172 | return error("split: empty separator") 173 | } 174 | 175 | ret = [] 176 | for (idx = Index(str, sep)) != -1 && n != 0 { 177 | if idx > 0 { 178 | ret = append(ret, slice(str, 0, idx)) 179 | } 180 | str = slice(str, idx + len(sep), len(str)) 181 | --n 182 | } 183 | 184 | return append(ret, str) 185 | } 186 | 187 | Split = fn(str, sep) { SplitN(str, sep, -1) } 188 | 189 | Fields = fn(str) { 190 | ret = [] 191 | for (i = IndexAny(str, spaces)) != -1 { 192 | ret = append(ret, slice(str, 0, i)) 193 | str = slice(str, i + 1, len(str)) 194 | } 195 | 196 | return append(ret, str) 197 | } 198 | 199 | TrimPrefix = fn(str, pre) { 200 | return if len(pre) > len(str) || pre != slice(str, 0, len(pre)) { 201 | str 202 | } else { 203 | slice(str, len(pre), len(str)) 204 | } 205 | } 206 | 207 | TrimSuffix = fn(str, sub) { 208 | return if len(sub) > len(str) || sub != slice(str, len(str)-len(sub), len(str)) { 209 | str 210 | } else { 211 | slice(str, 0, len(str)-len(sub)) 212 | } 213 | } 214 | 215 | ReplaceAll = fn(str, old, new) { Join(Split(str, old), new) } 216 | 217 | Replace = fn(str, old, new, n) { 218 | if n < 0 { 219 | return ReplaceAll(str, old, new) 220 | } 221 | 222 | ret = "" 223 | for i = 0; i < n; ++i { 224 | if (idx = Index(str, old)) == -1 { 225 | break 226 | } 227 | ret += slice(str, 0, idx) + new 228 | str = slice(str, idx+len(old), len(str)) 229 | } 230 | 231 | return ret + str 232 | } 233 | 234 | isalpha = fn(char) { char >= 97 && char <= 122 || char >= 65 && char <= 90 } 235 | islower = fn(char) { char >= 97 && char <= 122 } 236 | isupper = fn(char) { char >= 65 && char <= 90 } 237 | toupper = fn(char) { char - 32 } 238 | tolower = fn(char) { char + 32 } 239 | 240 | ToUpper = fn(str) { 241 | b = bytes(str) 242 | ret = [] 243 | 244 | for i = 0; i < len(b); ++i { 245 | char = b[i] 246 | ret = append(ret, if islower(char) { toupper(char) } else { char }) 247 | } 248 | 249 | return string(bytes(ret)) 250 | } 251 | 252 | ToLower = fn(str) { 253 | b = bytes(str) 254 | ret = [] 255 | 256 | for i = 0; i < len(b); ++i { 257 | char = b[i] 258 | ret = append(ret, if isupper(char) { tolower(char) } else { char }) 259 | } 260 | 261 | return string(bytes(ret)) 262 | } 263 | 264 | ToTitle = fn(str) { 265 | toks = Split(str, " ") 266 | ret = [] 267 | 268 | for i = 0; i < len(toks); ++i { 269 | b = bytes(toks[i]) 270 | tmp = [] 271 | 272 | tmp = append(tmp, if islower(b[0]) { toupper(b[0]) } else { b[0] }) 273 | for j = 1; j < len(b); ++j { 274 | tmp = append(tmp, b[j]) 275 | } 276 | 277 | ret = append(ret, string(bytes(tmp))) 278 | } 279 | 280 | return Join(ret, " ") 281 | } 282 | 283 | TrimLeft = fn(str, cutset) { 284 | for start = 0; start < len(str); ++start { 285 | if !Contains(cutset, str[start]) { 286 | break 287 | } 288 | } 289 | 290 | return slice(str, start, len(str)) 291 | } 292 | 293 | TrimRight = fn(str, cutset) { 294 | for stop = len(str); stop > 0; --stop { 295 | if !Contains(cutset, str[stop-1]) { 296 | break 297 | } 298 | } 299 | 300 | return slice(str, 0, stop) 301 | } 302 | 303 | Trim = fn(str, cutset) { TrimRight(TrimLeft(str, cutset), cutset) } 304 | 305 | TrimSpace = fn(str) { Trim(str, spaces) } 306 | -------------------------------------------------------------------------------- /tau.go: -------------------------------------------------------------------------------- 1 | package tau 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io" 7 | "os" 8 | "path/filepath" 9 | "runtime" 10 | "strings" 11 | 12 | "github.com/NicoNex/tau/internal/ast" 13 | "github.com/NicoNex/tau/internal/compiler" 14 | "github.com/NicoNex/tau/internal/parser" 15 | "github.com/NicoNex/tau/internal/vm" 16 | ) 17 | 18 | const TauVersion = "v2.0.15" 19 | 20 | var ErrParseError = errors.New("error: parse error") 21 | 22 | func readFile(fname string) []byte { 23 | b, err := os.ReadFile(fname) 24 | if err != nil { 25 | fmt.Println(err) 26 | os.Exit(1) 27 | } 28 | return b 29 | } 30 | 31 | func writeFile(fname string, cont []byte) { 32 | if err := os.WriteFile(fname, cont, 0644); err != nil { 33 | fmt.Println(err) 34 | os.Exit(1) 35 | } 36 | } 37 | 38 | func precompiledBytecode(path string) (compiler.Bytecode, error) { 39 | b, err := os.ReadFile(path) 40 | if err != nil { 41 | fmt.Println(err) 42 | return compiler.Bytecode{}, fmt.Errorf("error opening file %q: %w", path, err) 43 | } 44 | return compiler.DecodeBytecode(b), nil 45 | } 46 | 47 | func compile(path string) (bc compiler.Bytecode, err error) { 48 | input := string(readFile(path)) 49 | res, errs := parser.Parse(path, input) 50 | if len(errs) > 0 { 51 | var buf strings.Builder 52 | 53 | for _, e := range errs { 54 | buf.WriteString(e.Error()) 55 | buf.WriteByte('\n') 56 | } 57 | return compiler.Bytecode{}, errors.New(buf.String()) 58 | } 59 | 60 | c := compiler.New() 61 | c.SetFileInfo(path, input) 62 | if err = c.Compile(res); err != nil { 63 | return 64 | } 65 | 66 | return c.Bytecode(), nil 67 | } 68 | 69 | func ExecFileVM(f string) (err error) { 70 | var bytecode compiler.Bytecode 71 | 72 | if filepath.Ext(f) == ".tauc" { 73 | bytecode = compiler.DecodeBytecode(readFile(f)) 74 | } else { 75 | if bytecode, err = compile(f); err != nil { 76 | fmt.Println(err) 77 | return 78 | } 79 | } 80 | 81 | tvm := vm.New(f, bytecode) 82 | tvm.Run() 83 | return nil 84 | } 85 | 86 | func CompileFiles(files []string) error { 87 | for _, f := range files { 88 | b := readFile(f) 89 | 90 | res, errs := parser.Parse(f, string(b)) 91 | if len(errs) != 0 { 92 | for _, e := range errs { 93 | fmt.Println(e) 94 | } 95 | return ErrParseError 96 | } 97 | 98 | c := compiler.New() 99 | c.SetFileInfo(f, string(b)) 100 | if err := c.Compile(res); err != nil { 101 | fmt.Println(err) 102 | continue 103 | } 104 | ext := filepath.Ext(f) 105 | writeFile(f[:len(f)-len(ext)]+".tauc", c.Bytecode().Encode()) 106 | } 107 | 108 | return nil 109 | } 110 | 111 | func PrintVersionInfo(w io.Writer) { 112 | fmt.Fprintf(w, "Tau %s on %s\n", TauVersion, strings.Title(runtime.GOOS)) 113 | } 114 | 115 | func Parse(src string) (ast.Node, error) { 116 | tree, errs := parser.Parse("", src) 117 | if len(errs) > 0 { 118 | var buf strings.Builder 119 | 120 | buf.WriteString("parser error:\n") 121 | for _, e := range errs { 122 | buf.WriteString(e.Error()) 123 | buf.WriteByte('\n') 124 | } 125 | 126 | return nil, errors.New(buf.String()) 127 | } 128 | 129 | return tree, nil 130 | } 131 | --------------------------------------------------------------------------------