├── .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 |
--------------------------------------------------------------------------------