some code clean up (#237)
This commit is contained in:
parent
030b4b90ab
commit
c88a5f506e
297 changed files with 19913 additions and 19430 deletions
47
.circleci/config.yml
Normal file
47
.circleci/config.yml
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
version: 2
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
docker:
|
||||||
|
- image: circleci/golang:1.13
|
||||||
|
working_directory: /go/src/github.com/d5/tegno
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- restore_cache:
|
||||||
|
keys:
|
||||||
|
- go-mod-v1-{{ checksum "go.sum" }}
|
||||||
|
- run: curl -sL https://git.io/goreleaser | bash
|
||||||
|
- save_cache:
|
||||||
|
key: go-mod-v1-{{ checksum "go.sum" }}
|
||||||
|
paths:
|
||||||
|
- "/go/pkg/mod"
|
||||||
|
test:
|
||||||
|
docker:
|
||||||
|
- image: circleci/golang:1.13
|
||||||
|
working_directory: /go/src/github.com/d5/tegno
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- restore_cache:
|
||||||
|
keys:
|
||||||
|
- go-mod-v1-{{ checksum "go.sum" }}
|
||||||
|
- run: go get -u golang.org/x/lint/golint
|
||||||
|
- run: make test
|
||||||
|
- save_cache:
|
||||||
|
key: go-mod-v1-{{ checksum "go.sum" }}
|
||||||
|
paths:
|
||||||
|
- "/go/pkg/mod"
|
||||||
|
workflows:
|
||||||
|
version: 2
|
||||||
|
test:
|
||||||
|
jobs:
|
||||||
|
- test:
|
||||||
|
filters:
|
||||||
|
tags:
|
||||||
|
ignore: /.*/
|
||||||
|
release:
|
||||||
|
jobs:
|
||||||
|
- release:
|
||||||
|
filters:
|
||||||
|
branches:
|
||||||
|
ignore: /.*/
|
||||||
|
tags:
|
||||||
|
only: /v[0-9]+(\.[0-9]+)*(-.*)*/
|
|
@ -11,15 +11,6 @@ builds:
|
||||||
- darwin
|
- darwin
|
||||||
- linux
|
- linux
|
||||||
- windows
|
- windows
|
||||||
- env:
|
|
||||||
- CGO_ENABLED=0
|
|
||||||
main: ./cmd/tengomin/main.go
|
|
||||||
id: tengomin
|
|
||||||
binary: tengomin
|
|
||||||
goos:
|
|
||||||
- darwin
|
|
||||||
- linux
|
|
||||||
- windows
|
|
||||||
archive:
|
archive:
|
||||||
files:
|
files:
|
||||||
- none*
|
- none*
|
||||||
|
|
17
.travis.yml
17
.travis.yml
|
@ -1,17 +0,0 @@
|
||||||
language: go
|
|
||||||
|
|
||||||
go:
|
|
||||||
- "1.12"
|
|
||||||
|
|
||||||
install:
|
|
||||||
- env GO111MODULE=on go get -u golang.org/x/lint/golint
|
|
||||||
|
|
||||||
script:
|
|
||||||
- env GO111MODULE=on make test
|
|
||||||
|
|
||||||
deploy:
|
|
||||||
- provider: script
|
|
||||||
skip_cleanup: true
|
|
||||||
script: curl -sL https://git.io/goreleaser | bash
|
|
||||||
on:
|
|
||||||
tags: true
|
|
5
Makefile
5
Makefile
|
@ -1,13 +1,10 @@
|
||||||
vet:
|
|
||||||
go vet ./...
|
|
||||||
|
|
||||||
generate:
|
generate:
|
||||||
go generate ./...
|
go generate ./...
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
golint -set_exit_status ./...
|
golint -set_exit_status ./...
|
||||||
|
|
||||||
test: generate vet lint
|
test: generate lint
|
||||||
go test -race -cover ./...
|
go test -race -cover ./...
|
||||||
|
|
||||||
fmt:
|
fmt:
|
||||||
|
|
85
README.md
85
README.md
|
@ -4,18 +4,18 @@
|
||||||
|
|
||||||
# The Tengo Language
|
# The Tengo Language
|
||||||
|
|
||||||
[![GoDoc](https://godoc.org/github.com/d5/tengo?status.svg)](https://godoc.org/github.com/d5/tengo/script)
|
[![GoDoc](https://godoc.org/github.com/d5/tengo?status.svg)](https://godoc.org/github.com/d5/tengo)
|
||||||
[![Go Report Card](https://goreportcard.com/badge/github.com/d5/tengo)](https://goreportcard.com/report/github.com/d5/tengo)
|
[![Go Report Card](https://goreportcard.com/badge/github.com/d5/tengo)](https://goreportcard.com/report/github.com/d5/tengo)
|
||||||
[![Build Status](https://travis-ci.org/d5/tengo.svg?branch=master)](https://travis-ci.org/d5/tengo)
|
[![CircleCI](https://circleci.com/gh/d5/tengo.svg?style=svg)](https://circleci.com/gh/d5/tengo)
|
||||||
[![Sourcegraph](https://sourcegraph.com/github.com/d5/tengo/-/badge.svg)](https://sourcegraph.com/github.com/d5/tengo?badge)
|
[![Sourcegraph](https://sourcegraph.com/github.com/d5/tengo/-/badge.svg)](https://sourcegraph.com/github.com/d5/tengo?badge)
|
||||||
|
|
||||||
**Tengo is a small, dynamic, fast, secure script language for Go.**
|
**Tengo is a small, dynamic, fast, secure script language for Go.**
|
||||||
|
|
||||||
Tengo is **[fast](#benchmark)** and secure because it's compiled/executed as bytecode on stack-based VM that's written in native Go.
|
Tengo is **[fast](#benchmark)** and secure because it's compiled/executed as
|
||||||
|
bytecode on stack-based VM that's written in native Go.
|
||||||
|
|
||||||
```golang
|
```golang
|
||||||
/* The Tengo Language */
|
/* The Tengo Language */
|
||||||
|
|
||||||
fmt := import("fmt")
|
fmt := import("fmt")
|
||||||
|
|
||||||
each := func(seq, fn) {
|
each := func(seq, fn) {
|
||||||
|
@ -31,19 +31,24 @@ fmt.println(sum(0, [1, 2, 3])) // "6"
|
||||||
fmt.println(sum("", [1, 2, 3])) // "123"
|
fmt.println(sum("", [1, 2, 3])) // "123"
|
||||||
```
|
```
|
||||||
|
|
||||||
> Run this code in the [Playground](https://tengolang.com/?s=0c8d5d0d88f2795a7093d7f35ae12c3afa17bea3)
|
> Test this Tengo code in the
|
||||||
|
> [Tengo Playground](https://tengolang.com/?s=0c8d5d0d88f2795a7093d7f35ae12c3afa17bea3)
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Simple and highly readable [Syntax](https://github.com/d5/tengo/blob/master/docs/tutorial.md)
|
- Simple and highly readable
|
||||||
|
[Syntax](https://github.com/d5/tengo/blob/master/docs/tutorial.md)
|
||||||
- Dynamic typing with type coercion
|
- Dynamic typing with type coercion
|
||||||
- Higher-order functions and closures
|
- Higher-order functions and closures
|
||||||
- Immutable values
|
- Immutable values
|
||||||
- Garbage collection
|
- [Securely Embeddable](https://github.com/d5/tengo/blob/master/docs/interoperability.md)
|
||||||
- [Securely Embeddable](https://github.com/d5/tengo/blob/master/docs/interoperability.md) and [Extensible](https://github.com/d5/tengo/blob/master/docs/objects.md)
|
and [Extensible](https://github.com/d5/tengo/blob/master/docs/objects.md)
|
||||||
- Compiler/runtime written in native Go _(no external deps or cgo)_
|
- Compiler/runtime written in native Go _(no external deps or cgo)_
|
||||||
- Executable as a [standalone](https://github.com/d5/tengo/blob/master/docs/tengo-cli.md) language / REPL
|
- Executable as a
|
||||||
- Use cases: rules engine, [state machine](https://github.com/d5/go-fsm), [gaming](https://github.com/d5/pbr), data pipeline, [transpiler](https://github.com/d5/tengo2lua)
|
[standalone](https://github.com/d5/tengo/blob/master/docs/tengo-cli.md)
|
||||||
|
language / REPL
|
||||||
|
- Use cases: rules engine, [state machine](https://github.com/d5/go-fsm),
|
||||||
|
data pipeline, [transpiler](https://github.com/d5/tengo2lua)
|
||||||
|
|
||||||
## Benchmark
|
## Benchmark
|
||||||
|
|
||||||
|
@ -61,16 +66,70 @@ fmt.println(sum("", [1, 2, 3])) // "123"
|
||||||
| [otto](https://github.com/robertkrimen/otto) | `68,377ms` | `11ms` | JS Interpreter on Go |
|
| [otto](https://github.com/robertkrimen/otto) | `68,377ms` | `11ms` | JS Interpreter on Go |
|
||||||
| [Anko](https://github.com/mattn/anko) | `92,579ms` | `18ms` | Interpreter on Go |
|
| [Anko](https://github.com/mattn/anko) | `92,579ms` | `18ms` | Interpreter on Go |
|
||||||
|
|
||||||
_* [fib(35)](https://github.com/d5/tengobench/blob/master/code/fib.tengo): Fibonacci(35)_
|
_* [fib(35)](https://github.com/d5/tengobench/blob/master/code/fib.tengo):
|
||||||
_* [fibt(35)](https://github.com/d5/tengobench/blob/master/code/fibtc.tengo): [tail-call](https://en.wikipedia.org/wiki/Tail_call) version of Fibonacci(35)_
|
Fibonacci(35)_
|
||||||
|
_* [fibt(35)](https://github.com/d5/tengobench/blob/master/code/fibtc.tengo):
|
||||||
|
[tail-call](https://en.wikipedia.org/wiki/Tail_call) version of Fibonacci(35)_
|
||||||
_* **Go** does not read the source code from file, while all other cases do_
|
_* **Go** does not read the source code from file, while all other cases do_
|
||||||
_* See [here](https://github.com/d5/tengobench) for commands/codes used_
|
_* See [here](https://github.com/d5/tengobench) for commands/codes used_
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
A simple Go example code that compiles/runs Tengo script code with some input/output values:
|
||||||
|
|
||||||
|
```golang
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/d5/tengo"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Tengo script code
|
||||||
|
src := `
|
||||||
|
each := func(seq, fn) {
|
||||||
|
for x in seq { fn(x) }
|
||||||
|
}
|
||||||
|
|
||||||
|
sum := 0
|
||||||
|
mul := 1
|
||||||
|
each([a, b, c, d], func(x) {
|
||||||
|
sum += x
|
||||||
|
mul *= x
|
||||||
|
})`
|
||||||
|
|
||||||
|
// create a new Script instance
|
||||||
|
script := tengo.NewScript([]byte(src))
|
||||||
|
|
||||||
|
// set values
|
||||||
|
_ = script.Add("a", 1)
|
||||||
|
_ = script.Add("b", 9)
|
||||||
|
_ = script.Add("c", 8)
|
||||||
|
_ = script.Add("d", 4)
|
||||||
|
|
||||||
|
// run the script
|
||||||
|
compiled, err := script.RunContext(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// retrieve values
|
||||||
|
sum := compiled.Get("sum")
|
||||||
|
mul := compiled.Get("mul")
|
||||||
|
fmt.Println(sum, mul) // "22 288"
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
## References
|
## References
|
||||||
|
|
||||||
- [Language Syntax](https://github.com/d5/tengo/blob/master/docs/tutorial.md)
|
- [Language Syntax](https://github.com/d5/tengo/blob/master/docs/tutorial.md)
|
||||||
- [Object Types](https://github.com/d5/tengo/blob/master/docs/objects.md)
|
- [Object Types](https://github.com/d5/tengo/blob/master/docs/objects.md)
|
||||||
- [Runtime Types](https://github.com/d5/tengo/blob/master/docs/runtime-types.md) and [Operators](https://github.com/d5/tengo/blob/master/docs/operators.md)
|
- [Runtime Types](https://github.com/d5/tengo/blob/master/docs/runtime-types.md)
|
||||||
|
and [Operators](https://github.com/d5/tengo/blob/master/docs/operators.md)
|
||||||
- [Builtin Functions](https://github.com/d5/tengo/blob/master/docs/builtins.md)
|
- [Builtin Functions](https://github.com/d5/tengo/blob/master/docs/builtins.md)
|
||||||
- [Interoperability](https://github.com/d5/tengo/blob/master/docs/interoperability.md)
|
- [Interoperability](https://github.com/d5/tengo/blob/master/docs/interoperability.md)
|
||||||
- [Tengo CLI](https://github.com/d5/tengo/blob/master/docs/tengo-cli.md)
|
- [Tengo CLI](https://github.com/d5/tengo/blob/master/docs/tengo-cli.md)
|
||||||
|
|
356
assert/assert.go
356
assert/assert.go
|
@ -1,356 +0,0 @@
|
||||||
package assert
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/d5/tengo/compiler"
|
|
||||||
"github.com/d5/tengo/compiler/source"
|
|
||||||
"github.com/d5/tengo/compiler/token"
|
|
||||||
"github.com/d5/tengo/objects"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NoError asserts err is not an error.
|
|
||||||
func NoError(t *testing.T, err error, msg ...interface{}) bool {
|
|
||||||
if err == nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return failExpectedActual(t, "no error", err, msg...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error asserts err is an error.
|
|
||||||
func Error(t *testing.T, err error, msg ...interface{}) bool {
|
|
||||||
if err != nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return failExpectedActual(t, "error", err, msg...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Nil asserts v is nil.
|
|
||||||
func Nil(t *testing.T, v interface{}, msg ...interface{}) bool {
|
|
||||||
if isNil(v) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return failExpectedActual(t, "nil", v, msg...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// True asserts v is true.
|
|
||||||
func True(t *testing.T, v bool, msg ...interface{}) bool {
|
|
||||||
if v {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return failExpectedActual(t, "true", v, msg...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// False asserts vis false.
|
|
||||||
func False(t *testing.T, v bool, msg ...interface{}) bool {
|
|
||||||
if !v {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return failExpectedActual(t, "false", v, msg...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NotNil asserts v is not nil.
|
|
||||||
func NotNil(t *testing.T, v interface{}, msg ...interface{}) bool {
|
|
||||||
if !isNil(v) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return failExpectedActual(t, "not nil", v, msg...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsType asserts expected and actual are of the same type.
|
|
||||||
func IsType(t *testing.T, expected, actual interface{}, msg ...interface{}) bool {
|
|
||||||
if reflect.TypeOf(expected) == reflect.TypeOf(actual) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return failExpectedActual(t, reflect.TypeOf(expected), reflect.TypeOf(actual), msg...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Equal asserts expected and actual are equal.
|
|
||||||
func Equal(t *testing.T, expected, actual interface{}, msg ...interface{}) bool {
|
|
||||||
if isNil(expected) {
|
|
||||||
return Nil(t, actual, "expected nil, but got not nil")
|
|
||||||
}
|
|
||||||
if !NotNil(t, actual, "expected not nil, but got nil") {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if !IsType(t, expected, actual, msg...) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
switch expected := expected.(type) {
|
|
||||||
case int:
|
|
||||||
if expected != actual.(int) {
|
|
||||||
return failExpectedActual(t, expected, actual, msg...)
|
|
||||||
}
|
|
||||||
case int64:
|
|
||||||
if expected != actual.(int64) {
|
|
||||||
return failExpectedActual(t, expected, actual, msg...)
|
|
||||||
}
|
|
||||||
case float64:
|
|
||||||
if expected != actual.(float64) {
|
|
||||||
return failExpectedActual(t, expected, actual, msg...)
|
|
||||||
}
|
|
||||||
case string:
|
|
||||||
if expected != actual.(string) {
|
|
||||||
return failExpectedActual(t, expected, actual, msg...)
|
|
||||||
}
|
|
||||||
case []byte:
|
|
||||||
if !bytes.Equal(expected, actual.([]byte)) {
|
|
||||||
return failExpectedActual(t, string(expected), string(actual.([]byte)), msg...)
|
|
||||||
}
|
|
||||||
case []string:
|
|
||||||
if !equalStringSlice(expected, actual.([]string)) {
|
|
||||||
return failExpectedActual(t, expected, actual, msg...)
|
|
||||||
}
|
|
||||||
case []int:
|
|
||||||
if !equalIntSlice(expected, actual.([]int)) {
|
|
||||||
return failExpectedActual(t, expected, actual, msg...)
|
|
||||||
}
|
|
||||||
case bool:
|
|
||||||
if expected != actual.(bool) {
|
|
||||||
return failExpectedActual(t, expected, actual, msg...)
|
|
||||||
}
|
|
||||||
case rune:
|
|
||||||
if expected != actual.(rune) {
|
|
||||||
return failExpectedActual(t, expected, actual, msg...)
|
|
||||||
}
|
|
||||||
case *compiler.Symbol:
|
|
||||||
if !equalSymbol(expected, actual.(*compiler.Symbol)) {
|
|
||||||
return failExpectedActual(t, expected, actual, msg...)
|
|
||||||
}
|
|
||||||
case source.Pos:
|
|
||||||
if expected != actual.(source.Pos) {
|
|
||||||
return failExpectedActual(t, expected, actual, msg...)
|
|
||||||
}
|
|
||||||
case token.Token:
|
|
||||||
if expected != actual.(token.Token) {
|
|
||||||
return failExpectedActual(t, expected, actual, msg...)
|
|
||||||
}
|
|
||||||
case []objects.Object:
|
|
||||||
return equalObjectSlice(t, expected, actual.([]objects.Object), msg...)
|
|
||||||
case *objects.Int:
|
|
||||||
return Equal(t, expected.Value, actual.(*objects.Int).Value, msg...)
|
|
||||||
case *objects.Float:
|
|
||||||
return Equal(t, expected.Value, actual.(*objects.Float).Value, msg...)
|
|
||||||
case *objects.String:
|
|
||||||
return Equal(t, expected.Value, actual.(*objects.String).Value, msg...)
|
|
||||||
case *objects.Char:
|
|
||||||
return Equal(t, expected.Value, actual.(*objects.Char).Value, msg...)
|
|
||||||
case *objects.Bool:
|
|
||||||
if expected != actual {
|
|
||||||
return failExpectedActual(t, expected, actual, msg...)
|
|
||||||
}
|
|
||||||
case *objects.Array:
|
|
||||||
return equalObjectSlice(t, expected.Value, actual.(*objects.Array).Value, msg...)
|
|
||||||
case *objects.ImmutableArray:
|
|
||||||
return equalObjectSlice(t, expected.Value, actual.(*objects.ImmutableArray).Value, msg...)
|
|
||||||
case *objects.Bytes:
|
|
||||||
if !bytes.Equal(expected.Value, actual.(*objects.Bytes).Value) {
|
|
||||||
return failExpectedActual(t, string(expected.Value), string(actual.(*objects.Bytes).Value), msg...)
|
|
||||||
}
|
|
||||||
case *objects.Map:
|
|
||||||
return equalObjectMap(t, expected.Value, actual.(*objects.Map).Value, msg...)
|
|
||||||
case *objects.ImmutableMap:
|
|
||||||
return equalObjectMap(t, expected.Value, actual.(*objects.ImmutableMap).Value, msg...)
|
|
||||||
case *objects.CompiledFunction:
|
|
||||||
return equalCompiledFunction(t, expected, actual.(*objects.CompiledFunction), msg...)
|
|
||||||
case *objects.Closure:
|
|
||||||
return equalClosure(t, expected, actual.(*objects.Closure), msg...)
|
|
||||||
case *objects.Undefined:
|
|
||||||
if expected != actual {
|
|
||||||
return failExpectedActual(t, expected, actual, msg...)
|
|
||||||
}
|
|
||||||
case *objects.Error:
|
|
||||||
return Equal(t, expected.Value, actual.(*objects.Error).Value, msg...)
|
|
||||||
case objects.Object:
|
|
||||||
if !expected.Equals(actual.(objects.Object)) {
|
|
||||||
return failExpectedActual(t, expected, actual, msg...)
|
|
||||||
}
|
|
||||||
case *source.FileSet:
|
|
||||||
return equalFileSet(t, expected, actual.(*source.FileSet), msg...)
|
|
||||||
case *source.File:
|
|
||||||
return Equal(t, expected.Name, actual.(*source.File).Name, msg...) &&
|
|
||||||
Equal(t, expected.Base, actual.(*source.File).Base, msg...) &&
|
|
||||||
Equal(t, expected.Size, actual.(*source.File).Size, msg...) &&
|
|
||||||
True(t, equalIntSlice(expected.Lines, actual.(*source.File).Lines), msg...)
|
|
||||||
case error:
|
|
||||||
if expected != actual.(error) {
|
|
||||||
return failExpectedActual(t, expected, actual, msg...)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
panic(fmt.Errorf("type not implemented: %T", expected))
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fail marks the function as having failed but continues execution.
|
|
||||||
func Fail(t *testing.T, msg ...interface{}) bool {
|
|
||||||
t.Logf("\nError trace:\n\t%s\n%s", strings.Join(errorTrace(), "\n\t"), message(msg...))
|
|
||||||
|
|
||||||
t.Fail()
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func failExpectedActual(t *testing.T, expected, actual interface{}, msg ...interface{}) bool {
|
|
||||||
var addMsg string
|
|
||||||
if len(msg) > 0 {
|
|
||||||
addMsg = "\nMessage: " + message(msg...)
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Logf("\nError trace:\n\t%s\nExpected: %v\nActual: %v%s",
|
|
||||||
strings.Join(errorTrace(), "\n\t"),
|
|
||||||
expected, actual,
|
|
||||||
addMsg)
|
|
||||||
|
|
||||||
t.Fail()
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func message(formatArgs ...interface{}) string {
|
|
||||||
var format string
|
|
||||||
var args []interface{}
|
|
||||||
if len(formatArgs) > 0 {
|
|
||||||
format = formatArgs[0].(string)
|
|
||||||
}
|
|
||||||
if len(formatArgs) > 1 {
|
|
||||||
args = formatArgs[1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func equalIntSlice(a, b []int) bool {
|
|
||||||
if len(a) != len(b) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < len(a); i++ {
|
|
||||||
if a[i] != b[i] {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func equalStringSlice(a, b []string) bool {
|
|
||||||
if len(a) != len(b) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < len(a); i++ {
|
|
||||||
if a[i] != b[i] {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func equalSymbol(a, b *compiler.Symbol) bool {
|
|
||||||
return a.Name == b.Name &&
|
|
||||||
a.Index == b.Index &&
|
|
||||||
a.Scope == b.Scope
|
|
||||||
}
|
|
||||||
|
|
||||||
func equalObjectSlice(t *testing.T, expected, actual []objects.Object, msg ...interface{}) bool {
|
|
||||||
if !Equal(t, len(expected), len(actual), msg...) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < len(expected); i++ {
|
|
||||||
if !Equal(t, expected[i], actual[i], msg...) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func equalFileSet(t *testing.T, expected, actual *source.FileSet, msg ...interface{}) bool {
|
|
||||||
if !Equal(t, len(expected.Files), len(actual.Files), msg...) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for i, f := range expected.Files {
|
|
||||||
if !Equal(t, f, actual.Files[i], msg...) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Equal(t, expected.Base, actual.Base) &&
|
|
||||||
Equal(t, expected.LastFile, actual.LastFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
func equalObjectMap(t *testing.T, expected, actual map[string]objects.Object, msg ...interface{}) bool {
|
|
||||||
if !Equal(t, len(expected), len(actual), msg...) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
for key, expectedVal := range expected {
|
|
||||||
actualVal := actual[key]
|
|
||||||
|
|
||||||
if !Equal(t, expectedVal, actualVal, msg...) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func equalCompiledFunction(t *testing.T, expected, actual objects.Object, msg ...interface{}) bool {
|
|
||||||
expectedT := expected.(*objects.CompiledFunction)
|
|
||||||
actualT := actual.(*objects.CompiledFunction)
|
|
||||||
|
|
||||||
return Equal(t,
|
|
||||||
compiler.FormatInstructions(expectedT.Instructions, 0),
|
|
||||||
compiler.FormatInstructions(actualT.Instructions, 0), msg...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func equalClosure(t *testing.T, expected, actual objects.Object, msg ...interface{}) bool {
|
|
||||||
expectedT := expected.(*objects.Closure)
|
|
||||||
actualT := actual.(*objects.Closure)
|
|
||||||
|
|
||||||
if !Equal(t, expectedT.Fn, actualT.Fn, msg...) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if !Equal(t, len(expectedT.Free), len(actualT.Free), msg...) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < len(expectedT.Free); i++ {
|
|
||||||
if !Equal(t, *expectedT.Free[i], *actualT.Free[i], msg...) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func isNil(v interface{}) bool {
|
|
||||||
if v == nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
value := reflect.ValueOf(v)
|
|
||||||
kind := value.Kind()
|
|
||||||
if kind >= reflect.Chan && kind <= reflect.Slice && value.IsNil() {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
|
@ -1,72 +0,0 @@
|
||||||
package assert
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
"unicode"
|
|
||||||
"unicode/utf8"
|
|
||||||
)
|
|
||||||
|
|
||||||
func errorTrace() []string {
|
|
||||||
var pc uintptr
|
|
||||||
file := ""
|
|
||||||
line := 0
|
|
||||||
var ok bool
|
|
||||||
name := ""
|
|
||||||
|
|
||||||
var callers []string
|
|
||||||
for i := 0; ; i++ {
|
|
||||||
pc, file, line, ok = runtime.Caller(i)
|
|
||||||
if !ok {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if file == "<autogenerated>" {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
f := runtime.FuncForPC(pc)
|
|
||||||
if f == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
name = f.Name()
|
|
||||||
|
|
||||||
if name == "testing.tRunner" {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
parts := strings.Split(file, "/")
|
|
||||||
file = parts[len(parts)-1]
|
|
||||||
if len(parts) > 1 {
|
|
||||||
dir := parts[len(parts)-2]
|
|
||||||
if (dir != "assert" && dir != "mock" && dir != "require") || file == "mock_test.go" {
|
|
||||||
callers = append(callers, fmt.Sprintf("%s:%d", file, line))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Drop the package
|
|
||||||
segments := strings.Split(name, ".")
|
|
||||||
name = segments[len(segments)-1]
|
|
||||||
if isTest(name, "Test") ||
|
|
||||||
isTest(name, "Benchmark") ||
|
|
||||||
isTest(name, "Example") {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return callers
|
|
||||||
}
|
|
||||||
|
|
||||||
func isTest(name, prefix string) bool {
|
|
||||||
if !strings.HasPrefix(name, prefix) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if len(name) == len(prefix) { // "Test" is ok
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
r, _ := utf8.DecodeRuneInString(name[len(prefix):])
|
|
||||||
|
|
||||||
return !unicode.IsLower(r)
|
|
||||||
}
|
|
502
builtins.go
Normal file
502
builtins.go
Normal file
|
@ -0,0 +1,502 @@
|
||||||
|
package tengo
|
||||||
|
|
||||||
|
var builtinFuncs = []*BuiltinFunction{
|
||||||
|
{
|
||||||
|
Name: "len",
|
||||||
|
Value: builtinLen,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "copy",
|
||||||
|
Value: builtinCopy,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "append",
|
||||||
|
Value: builtinAppend,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "string",
|
||||||
|
Value: builtinString,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "int",
|
||||||
|
Value: builtinInt,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "bool",
|
||||||
|
Value: builtinBool,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "float",
|
||||||
|
Value: builtinFloat,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "char",
|
||||||
|
Value: builtinChar,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "bytes",
|
||||||
|
Value: builtinBytes,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "time",
|
||||||
|
Value: builtinTime,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "is_int",
|
||||||
|
Value: builtinIsInt,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "is_float",
|
||||||
|
Value: builtinIsFloat,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "is_string",
|
||||||
|
Value: builtinIsString,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "is_bool",
|
||||||
|
Value: builtinIsBool,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "is_char",
|
||||||
|
Value: builtinIsChar,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "is_bytes",
|
||||||
|
Value: builtinIsBytes,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "is_array",
|
||||||
|
Value: builtinIsArray,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "is_immutable_array",
|
||||||
|
Value: builtinIsImmutableArray,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "is_map",
|
||||||
|
Value: builtinIsMap,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "is_immutable_map",
|
||||||
|
Value: builtinIsImmutableMap,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "is_iterable",
|
||||||
|
Value: builtinIsIterable,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "is_time",
|
||||||
|
Value: builtinIsTime,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "is_error",
|
||||||
|
Value: builtinIsError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "is_undefined",
|
||||||
|
Value: builtinIsUndefined,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "is_function",
|
||||||
|
Value: builtinIsFunction,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "is_callable",
|
||||||
|
Value: builtinIsCallable,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "type_name",
|
||||||
|
Value: builtinTypeName,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "format",
|
||||||
|
Value: builtinFormat,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllBuiltinFunctions returns all builtin function objects.
|
||||||
|
func GetAllBuiltinFunctions() []*BuiltinFunction {
|
||||||
|
return append([]*BuiltinFunction{}, builtinFuncs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func builtinTypeName(args ...Object) (Object, error) {
|
||||||
|
if len(args) != 1 {
|
||||||
|
return nil, ErrWrongNumArguments
|
||||||
|
}
|
||||||
|
return &String{Value: args[0].TypeName()}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func builtinIsString(args ...Object) (Object, error) {
|
||||||
|
if len(args) != 1 {
|
||||||
|
return nil, ErrWrongNumArguments
|
||||||
|
}
|
||||||
|
if _, ok := args[0].(*String); ok {
|
||||||
|
return TrueValue, nil
|
||||||
|
}
|
||||||
|
return FalseValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func builtinIsInt(args ...Object) (Object, error) {
|
||||||
|
if len(args) != 1 {
|
||||||
|
return nil, ErrWrongNumArguments
|
||||||
|
}
|
||||||
|
if _, ok := args[0].(*Int); ok {
|
||||||
|
return TrueValue, nil
|
||||||
|
}
|
||||||
|
return FalseValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func builtinIsFloat(args ...Object) (Object, error) {
|
||||||
|
if len(args) != 1 {
|
||||||
|
return nil, ErrWrongNumArguments
|
||||||
|
}
|
||||||
|
if _, ok := args[0].(*Float); ok {
|
||||||
|
return TrueValue, nil
|
||||||
|
}
|
||||||
|
return FalseValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func builtinIsBool(args ...Object) (Object, error) {
|
||||||
|
if len(args) != 1 {
|
||||||
|
return nil, ErrWrongNumArguments
|
||||||
|
}
|
||||||
|
if _, ok := args[0].(*Bool); ok {
|
||||||
|
return TrueValue, nil
|
||||||
|
}
|
||||||
|
return FalseValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func builtinIsChar(args ...Object) (Object, error) {
|
||||||
|
if len(args) != 1 {
|
||||||
|
return nil, ErrWrongNumArguments
|
||||||
|
}
|
||||||
|
if _, ok := args[0].(*Char); ok {
|
||||||
|
return TrueValue, nil
|
||||||
|
}
|
||||||
|
return FalseValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func builtinIsBytes(args ...Object) (Object, error) {
|
||||||
|
if len(args) != 1 {
|
||||||
|
return nil, ErrWrongNumArguments
|
||||||
|
}
|
||||||
|
if _, ok := args[0].(*Bytes); ok {
|
||||||
|
return TrueValue, nil
|
||||||
|
}
|
||||||
|
return FalseValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func builtinIsArray(args ...Object) (Object, error) {
|
||||||
|
if len(args) != 1 {
|
||||||
|
return nil, ErrWrongNumArguments
|
||||||
|
}
|
||||||
|
if _, ok := args[0].(*Array); ok {
|
||||||
|
return TrueValue, nil
|
||||||
|
}
|
||||||
|
return FalseValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func builtinIsImmutableArray(args ...Object) (Object, error) {
|
||||||
|
if len(args) != 1 {
|
||||||
|
return nil, ErrWrongNumArguments
|
||||||
|
}
|
||||||
|
if _, ok := args[0].(*ImmutableArray); ok {
|
||||||
|
return TrueValue, nil
|
||||||
|
}
|
||||||
|
return FalseValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func builtinIsMap(args ...Object) (Object, error) {
|
||||||
|
if len(args) != 1 {
|
||||||
|
return nil, ErrWrongNumArguments
|
||||||
|
}
|
||||||
|
if _, ok := args[0].(*Map); ok {
|
||||||
|
return TrueValue, nil
|
||||||
|
}
|
||||||
|
return FalseValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func builtinIsImmutableMap(args ...Object) (Object, error) {
|
||||||
|
if len(args) != 1 {
|
||||||
|
return nil, ErrWrongNumArguments
|
||||||
|
}
|
||||||
|
if _, ok := args[0].(*ImmutableMap); ok {
|
||||||
|
return TrueValue, nil
|
||||||
|
}
|
||||||
|
return FalseValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func builtinIsTime(args ...Object) (Object, error) {
|
||||||
|
if len(args) != 1 {
|
||||||
|
return nil, ErrWrongNumArguments
|
||||||
|
}
|
||||||
|
if _, ok := args[0].(*Time); ok {
|
||||||
|
return TrueValue, nil
|
||||||
|
}
|
||||||
|
return FalseValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func builtinIsError(args ...Object) (Object, error) {
|
||||||
|
if len(args) != 1 {
|
||||||
|
return nil, ErrWrongNumArguments
|
||||||
|
}
|
||||||
|
if _, ok := args[0].(*Error); ok {
|
||||||
|
return TrueValue, nil
|
||||||
|
}
|
||||||
|
return FalseValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func builtinIsUndefined(args ...Object) (Object, error) {
|
||||||
|
if len(args) != 1 {
|
||||||
|
return nil, ErrWrongNumArguments
|
||||||
|
}
|
||||||
|
if args[0] == UndefinedValue {
|
||||||
|
return TrueValue, nil
|
||||||
|
}
|
||||||
|
return FalseValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func builtinIsFunction(args ...Object) (Object, error) {
|
||||||
|
if len(args) != 1 {
|
||||||
|
return nil, ErrWrongNumArguments
|
||||||
|
}
|
||||||
|
switch args[0].(type) {
|
||||||
|
case *CompiledFunction:
|
||||||
|
return TrueValue, nil
|
||||||
|
}
|
||||||
|
return FalseValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func builtinIsCallable(args ...Object) (Object, error) {
|
||||||
|
if len(args) != 1 {
|
||||||
|
return nil, ErrWrongNumArguments
|
||||||
|
}
|
||||||
|
if args[0].CanCall() {
|
||||||
|
return TrueValue, nil
|
||||||
|
}
|
||||||
|
return FalseValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func builtinIsIterable(args ...Object) (Object, error) {
|
||||||
|
if len(args) != 1 {
|
||||||
|
return nil, ErrWrongNumArguments
|
||||||
|
}
|
||||||
|
if args[0].CanIterate() {
|
||||||
|
return TrueValue, nil
|
||||||
|
}
|
||||||
|
return FalseValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// len(obj object) => int
|
||||||
|
func builtinLen(args ...Object) (Object, error) {
|
||||||
|
if len(args) != 1 {
|
||||||
|
return nil, ErrWrongNumArguments
|
||||||
|
}
|
||||||
|
switch arg := args[0].(type) {
|
||||||
|
case *Array:
|
||||||
|
return &Int{Value: int64(len(arg.Value))}, nil
|
||||||
|
case *ImmutableArray:
|
||||||
|
return &Int{Value: int64(len(arg.Value))}, nil
|
||||||
|
case *String:
|
||||||
|
return &Int{Value: int64(len(arg.Value))}, nil
|
||||||
|
case *Bytes:
|
||||||
|
return &Int{Value: int64(len(arg.Value))}, nil
|
||||||
|
case *Map:
|
||||||
|
return &Int{Value: int64(len(arg.Value))}, nil
|
||||||
|
case *ImmutableMap:
|
||||||
|
return &Int{Value: int64(len(arg.Value))}, nil
|
||||||
|
default:
|
||||||
|
return nil, ErrInvalidArgumentType{
|
||||||
|
Name: "first",
|
||||||
|
Expected: "array/string/bytes/map",
|
||||||
|
Found: arg.TypeName(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func builtinFormat(args ...Object) (Object, error) {
|
||||||
|
numArgs := len(args)
|
||||||
|
if numArgs == 0 {
|
||||||
|
return nil, ErrWrongNumArguments
|
||||||
|
}
|
||||||
|
format, ok := args[0].(*String)
|
||||||
|
if !ok {
|
||||||
|
return nil, ErrInvalidArgumentType{
|
||||||
|
Name: "format",
|
||||||
|
Expected: "string",
|
||||||
|
Found: args[0].TypeName(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if numArgs == 1 {
|
||||||
|
// okay to return 'format' directly as String is immutable
|
||||||
|
return format, nil
|
||||||
|
}
|
||||||
|
s, err := Format(format.Value, args[1:]...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &String{Value: s}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func builtinCopy(args ...Object) (Object, error) {
|
||||||
|
if len(args) != 1 {
|
||||||
|
return nil, ErrWrongNumArguments
|
||||||
|
}
|
||||||
|
return args[0].Copy(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func builtinString(args ...Object) (Object, error) {
|
||||||
|
argsLen := len(args)
|
||||||
|
if !(argsLen == 1 || argsLen == 2) {
|
||||||
|
return nil, ErrWrongNumArguments
|
||||||
|
}
|
||||||
|
if _, ok := args[0].(*String); ok {
|
||||||
|
return args[0], nil
|
||||||
|
}
|
||||||
|
v, ok := ToString(args[0])
|
||||||
|
if ok {
|
||||||
|
if len(v) > MaxStringLen {
|
||||||
|
return nil, ErrStringLimit
|
||||||
|
}
|
||||||
|
return &String{Value: v}, nil
|
||||||
|
}
|
||||||
|
if argsLen == 2 {
|
||||||
|
return args[1], nil
|
||||||
|
}
|
||||||
|
return UndefinedValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func builtinInt(args ...Object) (Object, error) {
|
||||||
|
argsLen := len(args)
|
||||||
|
if !(argsLen == 1 || argsLen == 2) {
|
||||||
|
return nil, ErrWrongNumArguments
|
||||||
|
}
|
||||||
|
if _, ok := args[0].(*Int); ok {
|
||||||
|
return args[0], nil
|
||||||
|
}
|
||||||
|
v, ok := ToInt64(args[0])
|
||||||
|
if ok {
|
||||||
|
return &Int{Value: v}, nil
|
||||||
|
}
|
||||||
|
if argsLen == 2 {
|
||||||
|
return args[1], nil
|
||||||
|
}
|
||||||
|
return UndefinedValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func builtinFloat(args ...Object) (Object, error) {
|
||||||
|
argsLen := len(args)
|
||||||
|
if !(argsLen == 1 || argsLen == 2) {
|
||||||
|
return nil, ErrWrongNumArguments
|
||||||
|
}
|
||||||
|
if _, ok := args[0].(*Float); ok {
|
||||||
|
return args[0], nil
|
||||||
|
}
|
||||||
|
v, ok := ToFloat64(args[0])
|
||||||
|
if ok {
|
||||||
|
return &Float{Value: v}, nil
|
||||||
|
}
|
||||||
|
if argsLen == 2 {
|
||||||
|
return args[1], nil
|
||||||
|
}
|
||||||
|
return UndefinedValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func builtinBool(args ...Object) (Object, error) {
|
||||||
|
if len(args) != 1 {
|
||||||
|
return nil, ErrWrongNumArguments
|
||||||
|
}
|
||||||
|
if _, ok := args[0].(*Bool); ok {
|
||||||
|
return args[0], nil
|
||||||
|
}
|
||||||
|
v, ok := ToBool(args[0])
|
||||||
|
if ok {
|
||||||
|
if v {
|
||||||
|
return TrueValue, nil
|
||||||
|
}
|
||||||
|
return FalseValue, nil
|
||||||
|
}
|
||||||
|
return UndefinedValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func builtinChar(args ...Object) (Object, error) {
|
||||||
|
argsLen := len(args)
|
||||||
|
if !(argsLen == 1 || argsLen == 2) {
|
||||||
|
return nil, ErrWrongNumArguments
|
||||||
|
}
|
||||||
|
if _, ok := args[0].(*Char); ok {
|
||||||
|
return args[0], nil
|
||||||
|
}
|
||||||
|
v, ok := ToRune(args[0])
|
||||||
|
if ok {
|
||||||
|
return &Char{Value: v}, nil
|
||||||
|
}
|
||||||
|
if argsLen == 2 {
|
||||||
|
return args[1], nil
|
||||||
|
}
|
||||||
|
return UndefinedValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func builtinBytes(args ...Object) (Object, error) {
|
||||||
|
argsLen := len(args)
|
||||||
|
if !(argsLen == 1 || argsLen == 2) {
|
||||||
|
return nil, ErrWrongNumArguments
|
||||||
|
}
|
||||||
|
|
||||||
|
// bytes(N) => create a new bytes with given size N
|
||||||
|
if n, ok := args[0].(*Int); ok {
|
||||||
|
if n.Value > int64(MaxBytesLen) {
|
||||||
|
return nil, ErrBytesLimit
|
||||||
|
}
|
||||||
|
return &Bytes{Value: make([]byte, int(n.Value))}, nil
|
||||||
|
}
|
||||||
|
v, ok := ToByteSlice(args[0])
|
||||||
|
if ok {
|
||||||
|
if len(v) > MaxBytesLen {
|
||||||
|
return nil, ErrBytesLimit
|
||||||
|
}
|
||||||
|
return &Bytes{Value: v}, nil
|
||||||
|
}
|
||||||
|
if argsLen == 2 {
|
||||||
|
return args[1], nil
|
||||||
|
}
|
||||||
|
return UndefinedValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func builtinTime(args ...Object) (Object, error) {
|
||||||
|
argsLen := len(args)
|
||||||
|
if !(argsLen == 1 || argsLen == 2) {
|
||||||
|
return nil, ErrWrongNumArguments
|
||||||
|
}
|
||||||
|
if _, ok := args[0].(*Time); ok {
|
||||||
|
return args[0], nil
|
||||||
|
}
|
||||||
|
v, ok := ToTime(args[0])
|
||||||
|
if ok {
|
||||||
|
return &Time{Value: v}, nil
|
||||||
|
}
|
||||||
|
if argsLen == 2 {
|
||||||
|
return args[1], nil
|
||||||
|
}
|
||||||
|
return UndefinedValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// append(arr, items...)
|
||||||
|
func builtinAppend(args ...Object) (Object, error) {
|
||||||
|
if len(args) < 2 {
|
||||||
|
return nil, ErrWrongNumArguments
|
||||||
|
}
|
||||||
|
switch arg := args[0].(type) {
|
||||||
|
case *Array:
|
||||||
|
return &Array{Value: append(arg.Value, args[1:]...)}, nil
|
||||||
|
case *ImmutableArray:
|
||||||
|
return &Array{Value: append(arg.Value, args[1:]...)}, nil
|
||||||
|
default:
|
||||||
|
return nil, ErrInvalidArgumentType{
|
||||||
|
Name: "first",
|
||||||
|
Expected: "array",
|
||||||
|
Found: arg.TypeName(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
292
bytecode.go
Normal file
292
bytecode.go
Normal file
|
@ -0,0 +1,292 @@
|
||||||
|
package tengo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/gob"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/d5/tengo/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Bytecode is a compiled instructions and constants.
|
||||||
|
type Bytecode struct {
|
||||||
|
FileSet *internal.SourceFileSet
|
||||||
|
MainFunction *CompiledFunction
|
||||||
|
Constants []Object
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode writes Bytecode data to the writer.
|
||||||
|
func (b *Bytecode) Encode(w io.Writer) error {
|
||||||
|
enc := gob.NewEncoder(w)
|
||||||
|
if err := enc.Encode(b.FileSet); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := enc.Encode(b.MainFunction); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return enc.Encode(b.Constants)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CountObjects returns the number of objects found in Constants.
|
||||||
|
func (b *Bytecode) CountObjects() int {
|
||||||
|
n := 0
|
||||||
|
for _, c := range b.Constants {
|
||||||
|
n += CountObjects(c)
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
// FormatInstructions returns human readable string representations of
|
||||||
|
// compiled instructions.
|
||||||
|
func (b *Bytecode) FormatInstructions() []string {
|
||||||
|
return internal.FormatInstructions(b.MainFunction.Instructions, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FormatConstants returns human readable string representations of
|
||||||
|
// compiled constants.
|
||||||
|
func (b *Bytecode) FormatConstants() (output []string) {
|
||||||
|
for cidx, cn := range b.Constants {
|
||||||
|
switch cn := cn.(type) {
|
||||||
|
case *CompiledFunction:
|
||||||
|
output = append(output, fmt.Sprintf(
|
||||||
|
"[% 3d] (Compiled Function|%p)", cidx, &cn))
|
||||||
|
for _, l := range internal.FormatInstructions(cn.Instructions, 0) {
|
||||||
|
output = append(output, fmt.Sprintf(" %s", l))
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
output = append(output, fmt.Sprintf("[% 3d] %s (%s|%p)",
|
||||||
|
cidx, cn, reflect.TypeOf(cn).Elem().Name(), &cn))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode reads Bytecode data from the reader.
|
||||||
|
func (b *Bytecode) Decode(r io.Reader, modules *ModuleMap) error {
|
||||||
|
if modules == nil {
|
||||||
|
modules = NewModuleMap()
|
||||||
|
}
|
||||||
|
|
||||||
|
dec := gob.NewDecoder(r)
|
||||||
|
if err := dec.Decode(&b.FileSet); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// TODO: files in b.FileSet.File does not have their 'set' field properly
|
||||||
|
// set to b.FileSet as it's private field and not serialized by gob
|
||||||
|
// encoder/decoder.
|
||||||
|
if err := dec.Decode(&b.MainFunction); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := dec.Decode(&b.Constants); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for i, v := range b.Constants {
|
||||||
|
fv, err := fixDecodedObject(v, modules)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
b.Constants[i] = fv
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveDuplicates finds and remove the duplicate values in Constants.
|
||||||
|
// Note this function mutates Bytecode.
|
||||||
|
func (b *Bytecode) RemoveDuplicates() {
|
||||||
|
var deduped []Object
|
||||||
|
|
||||||
|
indexMap := make(map[int]int) // mapping from old constant index to new index
|
||||||
|
ints := make(map[int64]int)
|
||||||
|
strings := make(map[string]int)
|
||||||
|
floats := make(map[float64]int)
|
||||||
|
chars := make(map[rune]int)
|
||||||
|
immutableMaps := make(map[string]int) // for modules
|
||||||
|
|
||||||
|
for curIdx, c := range b.Constants {
|
||||||
|
switch c := c.(type) {
|
||||||
|
case *CompiledFunction:
|
||||||
|
// add to deduped list
|
||||||
|
indexMap[curIdx] = len(deduped)
|
||||||
|
deduped = append(deduped, c)
|
||||||
|
case *ImmutableMap:
|
||||||
|
modName := inferModuleName(c)
|
||||||
|
newIdx, ok := immutableMaps[modName]
|
||||||
|
if modName != "" && ok {
|
||||||
|
indexMap[curIdx] = newIdx
|
||||||
|
} else {
|
||||||
|
newIdx = len(deduped)
|
||||||
|
immutableMaps[modName] = newIdx
|
||||||
|
indexMap[curIdx] = newIdx
|
||||||
|
deduped = append(deduped, c)
|
||||||
|
}
|
||||||
|
case *Int:
|
||||||
|
if newIdx, ok := ints[c.Value]; ok {
|
||||||
|
indexMap[curIdx] = newIdx
|
||||||
|
} else {
|
||||||
|
newIdx = len(deduped)
|
||||||
|
ints[c.Value] = newIdx
|
||||||
|
indexMap[curIdx] = newIdx
|
||||||
|
deduped = append(deduped, c)
|
||||||
|
}
|
||||||
|
case *String:
|
||||||
|
if newIdx, ok := strings[c.Value]; ok {
|
||||||
|
indexMap[curIdx] = newIdx
|
||||||
|
} else {
|
||||||
|
newIdx = len(deduped)
|
||||||
|
strings[c.Value] = newIdx
|
||||||
|
indexMap[curIdx] = newIdx
|
||||||
|
deduped = append(deduped, c)
|
||||||
|
}
|
||||||
|
case *Float:
|
||||||
|
if newIdx, ok := floats[c.Value]; ok {
|
||||||
|
indexMap[curIdx] = newIdx
|
||||||
|
} else {
|
||||||
|
newIdx = len(deduped)
|
||||||
|
floats[c.Value] = newIdx
|
||||||
|
indexMap[curIdx] = newIdx
|
||||||
|
deduped = append(deduped, c)
|
||||||
|
}
|
||||||
|
case *Char:
|
||||||
|
if newIdx, ok := chars[c.Value]; ok {
|
||||||
|
indexMap[curIdx] = newIdx
|
||||||
|
} else {
|
||||||
|
newIdx = len(deduped)
|
||||||
|
chars[c.Value] = newIdx
|
||||||
|
indexMap[curIdx] = newIdx
|
||||||
|
deduped = append(deduped, c)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
panic(fmt.Errorf("unsupported top-level constant type: %s",
|
||||||
|
c.TypeName()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// replace with de-duplicated constants
|
||||||
|
b.Constants = deduped
|
||||||
|
|
||||||
|
// update CONST instructions with new indexes
|
||||||
|
// main function
|
||||||
|
updateConstIndexes(b.MainFunction.Instructions, indexMap)
|
||||||
|
// other compiled functions in constants
|
||||||
|
for _, c := range b.Constants {
|
||||||
|
switch c := c.(type) {
|
||||||
|
case *CompiledFunction:
|
||||||
|
updateConstIndexes(c.Instructions, indexMap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fixDecodedObject(
|
||||||
|
o Object,
|
||||||
|
modules *ModuleMap,
|
||||||
|
) (Object, error) {
|
||||||
|
switch o := o.(type) {
|
||||||
|
case *Bool:
|
||||||
|
if o.IsFalsy() {
|
||||||
|
return FalseValue, nil
|
||||||
|
}
|
||||||
|
return TrueValue, nil
|
||||||
|
case *Undefined:
|
||||||
|
return UndefinedValue, nil
|
||||||
|
case *Array:
|
||||||
|
for i, v := range o.Value {
|
||||||
|
fv, err := fixDecodedObject(v, modules)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
o.Value[i] = fv
|
||||||
|
}
|
||||||
|
case *ImmutableArray:
|
||||||
|
for i, v := range o.Value {
|
||||||
|
fv, err := fixDecodedObject(v, modules)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
o.Value[i] = fv
|
||||||
|
}
|
||||||
|
case *Map:
|
||||||
|
for k, v := range o.Value {
|
||||||
|
fv, err := fixDecodedObject(v, modules)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
o.Value[k] = fv
|
||||||
|
}
|
||||||
|
case *ImmutableMap:
|
||||||
|
modName := inferModuleName(o)
|
||||||
|
if mod := modules.GetBuiltinModule(modName); mod != nil {
|
||||||
|
return mod.AsImmutableMap(modName), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range o.Value {
|
||||||
|
// encoding of user function not supported
|
||||||
|
if _, isUserFunction := v.(*UserFunction); isUserFunction {
|
||||||
|
return nil, fmt.Errorf("user function not decodable")
|
||||||
|
}
|
||||||
|
|
||||||
|
fv, err := fixDecodedObject(v, modules)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
o.Value[k] = fv
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return o, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateConstIndexes(insts []byte, indexMap map[int]int) {
|
||||||
|
i := 0
|
||||||
|
for i < len(insts) {
|
||||||
|
op := insts[i]
|
||||||
|
numOperands := internal.OpcodeOperands[op]
|
||||||
|
_, read := internal.ReadOperands(numOperands, insts[i+1:])
|
||||||
|
|
||||||
|
switch op {
|
||||||
|
case internal.OpConstant:
|
||||||
|
curIdx := int(insts[i+2]) | int(insts[i+1])<<8
|
||||||
|
newIdx, ok := indexMap[curIdx]
|
||||||
|
if !ok {
|
||||||
|
panic(fmt.Errorf("constant index not found: %d", curIdx))
|
||||||
|
}
|
||||||
|
copy(insts[i:], internal.MakeInstruction(op, newIdx))
|
||||||
|
case internal.OpClosure:
|
||||||
|
curIdx := int(insts[i+2]) | int(insts[i+1])<<8
|
||||||
|
numFree := int(insts[i+3])
|
||||||
|
newIdx, ok := indexMap[curIdx]
|
||||||
|
if !ok {
|
||||||
|
panic(fmt.Errorf("constant index not found: %d", curIdx))
|
||||||
|
}
|
||||||
|
copy(insts[i:], internal.MakeInstruction(op, newIdx, numFree))
|
||||||
|
}
|
||||||
|
|
||||||
|
i += 1 + read
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func inferModuleName(mod *ImmutableMap) string {
|
||||||
|
if modName, ok := mod.Value["__module_name__"].(*String); ok {
|
||||||
|
return modName.Value
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
gob.Register(&internal.SourceFileSet{})
|
||||||
|
gob.Register(&internal.SourceFile{})
|
||||||
|
gob.Register(&Array{})
|
||||||
|
gob.Register(&Bool{})
|
||||||
|
gob.Register(&Bytes{})
|
||||||
|
gob.Register(&Char{})
|
||||||
|
gob.Register(&CompiledFunction{})
|
||||||
|
gob.Register(&Error{})
|
||||||
|
gob.Register(&Float{})
|
||||||
|
gob.Register(&ImmutableArray{})
|
||||||
|
gob.Register(&ImmutableMap{})
|
||||||
|
gob.Register(&Int{})
|
||||||
|
gob.Register(&Map{})
|
||||||
|
gob.Register(&String{})
|
||||||
|
gob.Register(&Time{})
|
||||||
|
gob.Register(&Undefined{})
|
||||||
|
gob.Register(&UserFunction{})
|
||||||
|
}
|
298
bytecode_test.go
Normal file
298
bytecode_test.go
Normal file
|
@ -0,0 +1,298 @@
|
||||||
|
package tengo_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/d5/tengo"
|
||||||
|
"github.com/d5/tengo/internal"
|
||||||
|
"github.com/d5/tengo/internal/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
type srcfile struct {
|
||||||
|
name string
|
||||||
|
size int
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBytecode(t *testing.T) {
|
||||||
|
testBytecodeSerialization(t, bytecode(concatInsts(), objectsArray()))
|
||||||
|
|
||||||
|
testBytecodeSerialization(t, bytecode(
|
||||||
|
concatInsts(), objectsArray(
|
||||||
|
&tengo.Char{Value: 'y'},
|
||||||
|
&tengo.Float{Value: 93.11},
|
||||||
|
compiledFunction(1, 0,
|
||||||
|
internal.MakeInstruction(internal.OpConstant, 3),
|
||||||
|
internal.MakeInstruction(internal.OpSetLocal, 0),
|
||||||
|
internal.MakeInstruction(internal.OpGetGlobal, 0),
|
||||||
|
internal.MakeInstruction(internal.OpGetFree, 0)),
|
||||||
|
&tengo.Float{Value: 39.2},
|
||||||
|
&tengo.Int{Value: 192},
|
||||||
|
&tengo.String{Value: "bar"})))
|
||||||
|
|
||||||
|
testBytecodeSerialization(t, bytecodeFileSet(
|
||||||
|
concatInsts(
|
||||||
|
internal.MakeInstruction(internal.OpConstant, 0),
|
||||||
|
internal.MakeInstruction(internal.OpSetGlobal, 0),
|
||||||
|
internal.MakeInstruction(internal.OpConstant, 6),
|
||||||
|
internal.MakeInstruction(internal.OpPop)),
|
||||||
|
objectsArray(
|
||||||
|
&tengo.Int{Value: 55},
|
||||||
|
&tengo.Int{Value: 66},
|
||||||
|
&tengo.Int{Value: 77},
|
||||||
|
&tengo.Int{Value: 88},
|
||||||
|
&tengo.ImmutableMap{
|
||||||
|
Value: map[string]tengo.Object{
|
||||||
|
"array": &tengo.ImmutableArray{
|
||||||
|
Value: []tengo.Object{
|
||||||
|
&tengo.Int{Value: 1},
|
||||||
|
&tengo.Int{Value: 2},
|
||||||
|
&tengo.Int{Value: 3},
|
||||||
|
tengo.TrueValue,
|
||||||
|
tengo.FalseValue,
|
||||||
|
tengo.UndefinedValue,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"true": tengo.TrueValue,
|
||||||
|
"false": tengo.FalseValue,
|
||||||
|
"bytes": &tengo.Bytes{Value: make([]byte, 16)},
|
||||||
|
"char": &tengo.Char{Value: 'Y'},
|
||||||
|
"error": &tengo.Error{Value: &tengo.String{
|
||||||
|
Value: "some error",
|
||||||
|
}},
|
||||||
|
"float": &tengo.Float{Value: -19.84},
|
||||||
|
"immutable_array": &tengo.ImmutableArray{
|
||||||
|
Value: []tengo.Object{
|
||||||
|
&tengo.Int{Value: 1},
|
||||||
|
&tengo.Int{Value: 2},
|
||||||
|
&tengo.Int{Value: 3},
|
||||||
|
tengo.TrueValue,
|
||||||
|
tengo.FalseValue,
|
||||||
|
tengo.UndefinedValue,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"immutable_map": &tengo.ImmutableMap{
|
||||||
|
Value: map[string]tengo.Object{
|
||||||
|
"a": &tengo.Int{Value: 1},
|
||||||
|
"b": &tengo.Int{Value: 2},
|
||||||
|
"c": &tengo.Int{Value: 3},
|
||||||
|
"d": tengo.TrueValue,
|
||||||
|
"e": tengo.FalseValue,
|
||||||
|
"f": tengo.UndefinedValue,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"int": &tengo.Int{Value: 91},
|
||||||
|
"map": &tengo.Map{
|
||||||
|
Value: map[string]tengo.Object{
|
||||||
|
"a": &tengo.Int{Value: 1},
|
||||||
|
"b": &tengo.Int{Value: 2},
|
||||||
|
"c": &tengo.Int{Value: 3},
|
||||||
|
"d": tengo.TrueValue,
|
||||||
|
"e": tengo.FalseValue,
|
||||||
|
"f": tengo.UndefinedValue,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"string": &tengo.String{Value: "foo bar"},
|
||||||
|
"time": &tengo.Time{Value: time.Now()},
|
||||||
|
"undefined": tengo.UndefinedValue,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
compiledFunction(1, 0,
|
||||||
|
internal.MakeInstruction(internal.OpConstant, 3),
|
||||||
|
internal.MakeInstruction(internal.OpSetLocal, 0),
|
||||||
|
internal.MakeInstruction(internal.OpGetGlobal, 0),
|
||||||
|
internal.MakeInstruction(internal.OpGetFree, 0),
|
||||||
|
internal.MakeInstruction(internal.OpBinaryOp, 11),
|
||||||
|
internal.MakeInstruction(internal.OpGetFree, 1),
|
||||||
|
internal.MakeInstruction(internal.OpBinaryOp, 11),
|
||||||
|
internal.MakeInstruction(internal.OpGetLocal, 0),
|
||||||
|
internal.MakeInstruction(internal.OpBinaryOp, 11),
|
||||||
|
internal.MakeInstruction(internal.OpReturn, 1)),
|
||||||
|
compiledFunction(1, 0,
|
||||||
|
internal.MakeInstruction(internal.OpConstant, 2),
|
||||||
|
internal.MakeInstruction(internal.OpSetLocal, 0),
|
||||||
|
internal.MakeInstruction(internal.OpGetFree, 0),
|
||||||
|
internal.MakeInstruction(internal.OpGetLocal, 0),
|
||||||
|
internal.MakeInstruction(internal.OpClosure, 4, 2),
|
||||||
|
internal.MakeInstruction(internal.OpReturn, 1)),
|
||||||
|
compiledFunction(1, 0,
|
||||||
|
internal.MakeInstruction(internal.OpConstant, 1),
|
||||||
|
internal.MakeInstruction(internal.OpSetLocal, 0),
|
||||||
|
internal.MakeInstruction(internal.OpGetLocal, 0),
|
||||||
|
internal.MakeInstruction(internal.OpClosure, 5, 1),
|
||||||
|
internal.MakeInstruction(internal.OpReturn, 1))),
|
||||||
|
fileSet(srcfile{name: "file1", size: 100},
|
||||||
|
srcfile{name: "file2", size: 200})))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBytecode_RemoveDuplicates(t *testing.T) {
|
||||||
|
testBytecodeRemoveDuplicates(t,
|
||||||
|
bytecode(
|
||||||
|
concatInsts(), objectsArray(
|
||||||
|
&tengo.Char{Value: 'y'},
|
||||||
|
&tengo.Float{Value: 93.11},
|
||||||
|
compiledFunction(1, 0,
|
||||||
|
internal.MakeInstruction(internal.OpConstant, 3),
|
||||||
|
internal.MakeInstruction(internal.OpSetLocal, 0),
|
||||||
|
internal.MakeInstruction(internal.OpGetGlobal, 0),
|
||||||
|
internal.MakeInstruction(internal.OpGetFree, 0)),
|
||||||
|
&tengo.Float{Value: 39.2},
|
||||||
|
&tengo.Int{Value: 192},
|
||||||
|
&tengo.String{Value: "bar"})),
|
||||||
|
bytecode(
|
||||||
|
concatInsts(), objectsArray(
|
||||||
|
&tengo.Char{Value: 'y'},
|
||||||
|
&tengo.Float{Value: 93.11},
|
||||||
|
compiledFunction(1, 0,
|
||||||
|
internal.MakeInstruction(internal.OpConstant, 3),
|
||||||
|
internal.MakeInstruction(internal.OpSetLocal, 0),
|
||||||
|
internal.MakeInstruction(internal.OpGetGlobal, 0),
|
||||||
|
internal.MakeInstruction(internal.OpGetFree, 0)),
|
||||||
|
&tengo.Float{Value: 39.2},
|
||||||
|
&tengo.Int{Value: 192},
|
||||||
|
&tengo.String{Value: "bar"})))
|
||||||
|
|
||||||
|
testBytecodeRemoveDuplicates(t,
|
||||||
|
bytecode(
|
||||||
|
concatInsts(
|
||||||
|
internal.MakeInstruction(internal.OpConstant, 0),
|
||||||
|
internal.MakeInstruction(internal.OpConstant, 1),
|
||||||
|
internal.MakeInstruction(internal.OpConstant, 2),
|
||||||
|
internal.MakeInstruction(internal.OpConstant, 3),
|
||||||
|
internal.MakeInstruction(internal.OpConstant, 4),
|
||||||
|
internal.MakeInstruction(internal.OpConstant, 5),
|
||||||
|
internal.MakeInstruction(internal.OpConstant, 6),
|
||||||
|
internal.MakeInstruction(internal.OpConstant, 7),
|
||||||
|
internal.MakeInstruction(internal.OpConstant, 8),
|
||||||
|
internal.MakeInstruction(internal.OpClosure, 4, 1)),
|
||||||
|
objectsArray(
|
||||||
|
&tengo.Int{Value: 1},
|
||||||
|
&tengo.Float{Value: 2.0},
|
||||||
|
&tengo.Char{Value: '3'},
|
||||||
|
&tengo.String{Value: "four"},
|
||||||
|
compiledFunction(1, 0,
|
||||||
|
internal.MakeInstruction(internal.OpConstant, 3),
|
||||||
|
internal.MakeInstruction(internal.OpConstant, 7),
|
||||||
|
internal.MakeInstruction(internal.OpSetLocal, 0),
|
||||||
|
internal.MakeInstruction(internal.OpGetGlobal, 0),
|
||||||
|
internal.MakeInstruction(internal.OpGetFree, 0)),
|
||||||
|
&tengo.Int{Value: 1},
|
||||||
|
&tengo.Float{Value: 2.0},
|
||||||
|
&tengo.Char{Value: '3'},
|
||||||
|
&tengo.String{Value: "four"})),
|
||||||
|
bytecode(
|
||||||
|
concatInsts(
|
||||||
|
internal.MakeInstruction(internal.OpConstant, 0),
|
||||||
|
internal.MakeInstruction(internal.OpConstant, 1),
|
||||||
|
internal.MakeInstruction(internal.OpConstant, 2),
|
||||||
|
internal.MakeInstruction(internal.OpConstant, 3),
|
||||||
|
internal.MakeInstruction(internal.OpConstant, 4),
|
||||||
|
internal.MakeInstruction(internal.OpConstant, 0),
|
||||||
|
internal.MakeInstruction(internal.OpConstant, 1),
|
||||||
|
internal.MakeInstruction(internal.OpConstant, 2),
|
||||||
|
internal.MakeInstruction(internal.OpConstant, 3),
|
||||||
|
internal.MakeInstruction(internal.OpClosure, 4, 1)),
|
||||||
|
objectsArray(
|
||||||
|
&tengo.Int{Value: 1},
|
||||||
|
&tengo.Float{Value: 2.0},
|
||||||
|
&tengo.Char{Value: '3'},
|
||||||
|
&tengo.String{Value: "four"},
|
||||||
|
compiledFunction(1, 0,
|
||||||
|
internal.MakeInstruction(internal.OpConstant, 3),
|
||||||
|
internal.MakeInstruction(internal.OpConstant, 2),
|
||||||
|
internal.MakeInstruction(internal.OpSetLocal, 0),
|
||||||
|
internal.MakeInstruction(internal.OpGetGlobal, 0),
|
||||||
|
internal.MakeInstruction(internal.OpGetFree, 0)))))
|
||||||
|
|
||||||
|
testBytecodeRemoveDuplicates(t,
|
||||||
|
bytecode(
|
||||||
|
concatInsts(
|
||||||
|
internal.MakeInstruction(internal.OpConstant, 0),
|
||||||
|
internal.MakeInstruction(internal.OpConstant, 1),
|
||||||
|
internal.MakeInstruction(internal.OpConstant, 2),
|
||||||
|
internal.MakeInstruction(internal.OpConstant, 3),
|
||||||
|
internal.MakeInstruction(internal.OpConstant, 4)),
|
||||||
|
objectsArray(
|
||||||
|
&tengo.Int{Value: 1},
|
||||||
|
&tengo.Int{Value: 2},
|
||||||
|
&tengo.Int{Value: 3},
|
||||||
|
&tengo.Int{Value: 1},
|
||||||
|
&tengo.Int{Value: 3})),
|
||||||
|
bytecode(
|
||||||
|
concatInsts(
|
||||||
|
internal.MakeInstruction(internal.OpConstant, 0),
|
||||||
|
internal.MakeInstruction(internal.OpConstant, 1),
|
||||||
|
internal.MakeInstruction(internal.OpConstant, 2),
|
||||||
|
internal.MakeInstruction(internal.OpConstant, 0),
|
||||||
|
internal.MakeInstruction(internal.OpConstant, 2)),
|
||||||
|
objectsArray(
|
||||||
|
&tengo.Int{Value: 1},
|
||||||
|
&tengo.Int{Value: 2},
|
||||||
|
&tengo.Int{Value: 3})))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBytecode_CountObjects(t *testing.T) {
|
||||||
|
b := bytecode(
|
||||||
|
concatInsts(),
|
||||||
|
objectsArray(
|
||||||
|
&tengo.Int{Value: 55},
|
||||||
|
&tengo.Int{Value: 66},
|
||||||
|
&tengo.Int{Value: 77},
|
||||||
|
&tengo.Int{Value: 88},
|
||||||
|
compiledFunction(1, 0,
|
||||||
|
internal.MakeInstruction(internal.OpConstant, 3),
|
||||||
|
internal.MakeInstruction(internal.OpReturn, 1)),
|
||||||
|
compiledFunction(1, 0,
|
||||||
|
internal.MakeInstruction(internal.OpConstant, 2),
|
||||||
|
internal.MakeInstruction(internal.OpReturn, 1)),
|
||||||
|
compiledFunction(1, 0,
|
||||||
|
internal.MakeInstruction(internal.OpConstant, 1),
|
||||||
|
internal.MakeInstruction(internal.OpReturn, 1))))
|
||||||
|
require.Equal(t, 7, b.CountObjects())
|
||||||
|
}
|
||||||
|
|
||||||
|
func fileSet(files ...srcfile) *internal.SourceFileSet {
|
||||||
|
fileSet := internal.NewFileSet()
|
||||||
|
for _, f := range files {
|
||||||
|
fileSet.AddFile(f.name, -1, f.size)
|
||||||
|
}
|
||||||
|
return fileSet
|
||||||
|
}
|
||||||
|
|
||||||
|
func bytecodeFileSet(
|
||||||
|
instructions []byte,
|
||||||
|
constants []tengo.Object,
|
||||||
|
fileSet *internal.SourceFileSet,
|
||||||
|
) *tengo.Bytecode {
|
||||||
|
return &tengo.Bytecode{
|
||||||
|
FileSet: fileSet,
|
||||||
|
MainFunction: &tengo.CompiledFunction{Instructions: instructions},
|
||||||
|
Constants: constants,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testBytecodeRemoveDuplicates(
|
||||||
|
t *testing.T,
|
||||||
|
input, expected *tengo.Bytecode,
|
||||||
|
) {
|
||||||
|
input.RemoveDuplicates()
|
||||||
|
|
||||||
|
require.Equal(t, expected.FileSet, input.FileSet)
|
||||||
|
require.Equal(t, expected.MainFunction, input.MainFunction)
|
||||||
|
require.Equal(t, expected.Constants, input.Constants)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testBytecodeSerialization(t *testing.T, b *tengo.Bytecode) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := b.Encode(&buf)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
r := &tengo.Bytecode{}
|
||||||
|
err = r.Decode(bytes.NewReader(buf.Bytes()), nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, b.FileSet, r.FileSet)
|
||||||
|
require.Equal(t, b.MainFunction, r.MainFunction)
|
||||||
|
require.Equal(t, b.Constants, r.Constants)
|
||||||
|
}
|
329
cli/cli.go
329
cli/cli.go
|
@ -1,329 +0,0 @@
|
||||||
package cli
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/d5/tengo/compiler"
|
|
||||||
"github.com/d5/tengo/compiler/ast"
|
|
||||||
"github.com/d5/tengo/compiler/parser"
|
|
||||||
"github.com/d5/tengo/compiler/source"
|
|
||||||
"github.com/d5/tengo/objects"
|
|
||||||
"github.com/d5/tengo/runtime"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
sourceFileExt = ".tengo"
|
|
||||||
replPrompt = ">> "
|
|
||||||
)
|
|
||||||
|
|
||||||
// Options represent CLI options
|
|
||||||
type Options struct {
|
|
||||||
// Compile output file
|
|
||||||
CompileOutput string
|
|
||||||
|
|
||||||
// Show help flag
|
|
||||||
ShowHelp bool
|
|
||||||
|
|
||||||
// Show version flag
|
|
||||||
ShowVersion bool
|
|
||||||
|
|
||||||
// Input file
|
|
||||||
InputFile string
|
|
||||||
|
|
||||||
// Version
|
|
||||||
Version string
|
|
||||||
|
|
||||||
// Import modules
|
|
||||||
Modules *objects.ModuleMap
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run CLI
|
|
||||||
func Run(options *Options) {
|
|
||||||
if options.ShowHelp {
|
|
||||||
doHelp()
|
|
||||||
os.Exit(2)
|
|
||||||
} else if options.ShowVersion {
|
|
||||||
fmt.Println(options.Version)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if options.InputFile == "" {
|
|
||||||
// REPL
|
|
||||||
RunREPL(options.Modules, os.Stdin, os.Stdout)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
inputData, err := ioutil.ReadFile(options.InputFile)
|
|
||||||
if err != nil {
|
|
||||||
_, _ = fmt.Fprintf(os.Stderr, "Error reading input file: %s", err.Error())
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if options.CompileOutput != "" {
|
|
||||||
if err := CompileOnly(options.Modules, inputData, options.InputFile, options.CompileOutput); err != nil {
|
|
||||||
_, _ = fmt.Fprintln(os.Stderr, err.Error())
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
} else if filepath.Ext(options.InputFile) == sourceFileExt {
|
|
||||||
if err := CompileAndRun(options.Modules, inputData, options.InputFile); err != nil {
|
|
||||||
_, _ = fmt.Fprintln(os.Stderr, err.Error())
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if err := RunCompiled(options.Modules, inputData); err != nil {
|
|
||||||
_, _ = fmt.Fprintln(os.Stderr, err.Error())
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func doHelp() {
|
|
||||||
fmt.Println("Usage:")
|
|
||||||
fmt.Println()
|
|
||||||
fmt.Println(" tengo [flags] {input-file}")
|
|
||||||
fmt.Println()
|
|
||||||
fmt.Println("Flags:")
|
|
||||||
fmt.Println()
|
|
||||||
fmt.Println(" -o compile output file")
|
|
||||||
fmt.Println(" -version show version")
|
|
||||||
fmt.Println()
|
|
||||||
fmt.Println("Examples:")
|
|
||||||
fmt.Println()
|
|
||||||
fmt.Println(" tengo")
|
|
||||||
fmt.Println()
|
|
||||||
fmt.Println(" Start Tengo REPL")
|
|
||||||
fmt.Println()
|
|
||||||
fmt.Println(" tengo myapp.tengo")
|
|
||||||
fmt.Println()
|
|
||||||
fmt.Println(" Compile and run source file (myapp.tengo)")
|
|
||||||
fmt.Println(" Source file must have .tengo extension")
|
|
||||||
fmt.Println()
|
|
||||||
fmt.Println(" tengo -o myapp myapp.tengo")
|
|
||||||
fmt.Println()
|
|
||||||
fmt.Println(" Compile source file (myapp.tengo) into bytecode file (myapp)")
|
|
||||||
fmt.Println()
|
|
||||||
fmt.Println(" tengo myapp")
|
|
||||||
fmt.Println()
|
|
||||||
fmt.Println(" Run bytecode file (myapp)")
|
|
||||||
fmt.Println()
|
|
||||||
fmt.Println()
|
|
||||||
}
|
|
||||||
|
|
||||||
// CompileOnly compiles the source code and writes the compiled binary into outputFile.
|
|
||||||
func CompileOnly(modules *objects.ModuleMap, data []byte, inputFile, outputFile string) (err error) {
|
|
||||||
bytecode, err := compileSrc(modules, data, filepath.Base(inputFile))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if outputFile == "" {
|
|
||||||
outputFile = basename(inputFile) + ".out"
|
|
||||||
}
|
|
||||||
|
|
||||||
out, err := os.OpenFile(outputFile, os.O_CREATE|os.O_WRONLY, os.ModePerm)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
if err != nil {
|
|
||||||
_ = out.Close()
|
|
||||||
} else {
|
|
||||||
err = out.Close()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
err = bytecode.Encode(out)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println(outputFile)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// CompileAndRun compiles the source code and executes it.
|
|
||||||
func CompileAndRun(modules *objects.ModuleMap, data []byte, inputFile string) (err error) {
|
|
||||||
bytecode, err := compileSrc(modules, data, filepath.Base(inputFile))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
machine := runtime.NewVM(bytecode, nil, -1)
|
|
||||||
|
|
||||||
err = machine.Run()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// RunCompiled reads the compiled binary from file and executes it.
|
|
||||||
func RunCompiled(modules *objects.ModuleMap, data []byte) (err error) {
|
|
||||||
bytecode := &compiler.Bytecode{}
|
|
||||||
err = bytecode.Decode(bytes.NewReader(data), modules)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
machine := runtime.NewVM(bytecode, nil, -1)
|
|
||||||
|
|
||||||
err = machine.Run()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// RunREPL starts REPL.
|
|
||||||
func RunREPL(modules *objects.ModuleMap, in io.Reader, out io.Writer) {
|
|
||||||
stdin := bufio.NewScanner(in)
|
|
||||||
|
|
||||||
fileSet := source.NewFileSet()
|
|
||||||
globals := make([]objects.Object, runtime.GlobalsSize)
|
|
||||||
|
|
||||||
symbolTable := compiler.NewSymbolTable()
|
|
||||||
for idx, fn := range objects.Builtins {
|
|
||||||
symbolTable.DefineBuiltin(idx, fn.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// embed println function
|
|
||||||
symbol := symbolTable.Define("__repl_println__")
|
|
||||||
globals[symbol.Index] = &objects.UserFunction{
|
|
||||||
Name: "println",
|
|
||||||
Value: func(args ...objects.Object) (ret objects.Object, err error) {
|
|
||||||
var printArgs []interface{}
|
|
||||||
for _, arg := range args {
|
|
||||||
if _, isUndefined := arg.(*objects.Undefined); isUndefined {
|
|
||||||
printArgs = append(printArgs, "<undefined>")
|
|
||||||
} else {
|
|
||||||
s, _ := objects.ToString(arg)
|
|
||||||
printArgs = append(printArgs, s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
printArgs = append(printArgs, "\n")
|
|
||||||
_, _ = fmt.Print(printArgs...)
|
|
||||||
|
|
||||||
return
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
var constants []objects.Object
|
|
||||||
|
|
||||||
for {
|
|
||||||
_, _ = fmt.Fprint(out, replPrompt)
|
|
||||||
|
|
||||||
scanned := stdin.Scan()
|
|
||||||
if !scanned {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
line := stdin.Text()
|
|
||||||
|
|
||||||
srcFile := fileSet.AddFile("repl", -1, len(line))
|
|
||||||
p := parser.NewParser(srcFile, []byte(line), nil)
|
|
||||||
file, err := p.ParseFile()
|
|
||||||
if err != nil {
|
|
||||||
_, _ = fmt.Fprintln(out, err.Error())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
file = addPrints(file)
|
|
||||||
|
|
||||||
c := compiler.NewCompiler(srcFile, symbolTable, constants, modules, nil)
|
|
||||||
if err := c.Compile(file); err != nil {
|
|
||||||
_, _ = fmt.Fprintln(out, err.Error())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
bytecode := c.Bytecode()
|
|
||||||
|
|
||||||
machine := runtime.NewVM(bytecode, globals, -1)
|
|
||||||
if err := machine.Run(); err != nil {
|
|
||||||
_, _ = fmt.Fprintln(out, err.Error())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
constants = bytecode.Constants
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func compileSrc(modules *objects.ModuleMap, src []byte, filename string) (*compiler.Bytecode, error) {
|
|
||||||
fileSet := source.NewFileSet()
|
|
||||||
srcFile := fileSet.AddFile(filename, -1, len(src))
|
|
||||||
|
|
||||||
p := parser.NewParser(srcFile, src, nil)
|
|
||||||
file, err := p.ParseFile()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
c := compiler.NewCompiler(srcFile, nil, nil, modules, nil)
|
|
||||||
c.EnableFileImport(true)
|
|
||||||
|
|
||||||
if err := c.Compile(file); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
bytecode := c.Bytecode()
|
|
||||||
bytecode.RemoveDuplicates()
|
|
||||||
|
|
||||||
return bytecode, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func addPrints(file *ast.File) *ast.File {
|
|
||||||
var stmts []ast.Stmt
|
|
||||||
|
|
||||||
for _, s := range file.Stmts {
|
|
||||||
switch s := s.(type) {
|
|
||||||
case *ast.ExprStmt:
|
|
||||||
stmts = append(stmts, &ast.ExprStmt{
|
|
||||||
Expr: &ast.CallExpr{
|
|
||||||
Func: &ast.Ident{Name: "__repl_println__"},
|
|
||||||
Args: []ast.Expr{s.Expr},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
case *ast.AssignStmt:
|
|
||||||
stmts = append(stmts, s)
|
|
||||||
|
|
||||||
stmts = append(stmts, &ast.ExprStmt{
|
|
||||||
Expr: &ast.CallExpr{
|
|
||||||
Func: &ast.Ident{
|
|
||||||
Name: "__repl_println__",
|
|
||||||
},
|
|
||||||
Args: s.LHS,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
default:
|
|
||||||
stmts = append(stmts, s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &ast.File{
|
|
||||||
InputFile: file.InputFile,
|
|
||||||
Stmts: stmts,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func basename(s string) string {
|
|
||||||
s = filepath.Base(s)
|
|
||||||
|
|
||||||
n := strings.LastIndexByte(s, '.')
|
|
||||||
if n > 0 {
|
|
||||||
return s[:n]
|
|
||||||
}
|
|
||||||
|
|
||||||
return s
|
|
||||||
}
|
|
|
@ -1,64 +0,0 @@
|
||||||
package cli_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/d5/tengo/assert"
|
|
||||||
"github.com/d5/tengo/cli"
|
|
||||||
"github.com/d5/tengo/stdlib"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestCLICompileAndRun(t *testing.T) {
|
|
||||||
tempDir := filepath.Join(os.TempDir(), "tengo_tests")
|
|
||||||
_ = os.MkdirAll(tempDir, os.ModePerm)
|
|
||||||
binFile := filepath.Join(tempDir, "cli_bin")
|
|
||||||
outFile := filepath.Join(tempDir, "cli_out")
|
|
||||||
defer func() {
|
|
||||||
_ = os.RemoveAll(tempDir)
|
|
||||||
}()
|
|
||||||
|
|
||||||
src := []byte(`
|
|
||||||
os := import("os")
|
|
||||||
rand := import("rand")
|
|
||||||
times := import("times")
|
|
||||||
|
|
||||||
rand.seed(times.time_nanosecond(times.now()))
|
|
||||||
|
|
||||||
rand_num := func() {
|
|
||||||
return rand.intn(100)
|
|
||||||
}
|
|
||||||
|
|
||||||
file := os.create("` + outFile + `")
|
|
||||||
file.write_string("random number is " + rand_num())
|
|
||||||
file.close()
|
|
||||||
`)
|
|
||||||
|
|
||||||
mods := stdlib.GetModuleMap(stdlib.AllModuleNames()...)
|
|
||||||
|
|
||||||
err := cli.CompileOnly(mods, src, "src", binFile)
|
|
||||||
if !assert.NoError(t, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
compiledBin, err := ioutil.ReadFile(binFile)
|
|
||||||
if !assert.NoError(t, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = cli.RunCompiled(mods, compiledBin)
|
|
||||||
if !assert.NoError(t, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
read, err := ioutil.ReadFile(outFile)
|
|
||||||
if !assert.NoError(t, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ok, err := regexp.Match(`^random number is \d+$`, read)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.True(t, ok, string(read))
|
|
||||||
}
|
|
|
@ -4,12 +4,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/d5/tengo/compiler"
|
"github.com/d5/tengo"
|
||||||
"github.com/d5/tengo/compiler/ast"
|
"github.com/d5/tengo/internal"
|
||||||
"github.com/d5/tengo/compiler/parser"
|
|
||||||
"github.com/d5/tengo/compiler/source"
|
|
||||||
"github.com/d5/tengo/objects"
|
|
||||||
"github.com/d5/tengo/runtime"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -40,8 +36,9 @@ fib := func(x) {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if nativeResult != int(result.(*objects.Int).Value) {
|
if nativeResult != int(result.(*tengo.Int).Value) {
|
||||||
panic(fmt.Errorf("wrong result: %d != %d", nativeResult, int(result.(*objects.Int).Value)))
|
panic(fmt.Errorf("wrong result: %d != %d", nativeResult,
|
||||||
|
int(result.(*tengo.Int).Value)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("-------------------------------------")
|
fmt.Println("-------------------------------------")
|
||||||
|
@ -76,8 +73,9 @@ fib := func(x, s) {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if nativeResult != int(result.(*objects.Int).Value) {
|
if nativeResult != int(result.(*tengo.Int).Value) {
|
||||||
panic(fmt.Errorf("wrong result: %d != %d", nativeResult, int(result.(*objects.Int).Value)))
|
panic(fmt.Errorf("wrong result: %d != %d", nativeResult,
|
||||||
|
int(result.(*tengo.Int).Value)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("-------------------------------------")
|
fmt.Println("-------------------------------------")
|
||||||
|
@ -112,8 +110,9 @@ fib := func(x, a, b) {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if nativeResult != int(result.(*objects.Int).Value) {
|
if nativeResult != int(result.(*tengo.Int).Value) {
|
||||||
panic(fmt.Errorf("wrong result: %d != %d", nativeResult, int(result.(*objects.Int).Value)))
|
panic(fmt.Errorf("wrong result: %d != %d", nativeResult,
|
||||||
|
int(result.(*tengo.Int).Value)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("-------------------------------------")
|
fmt.Println("-------------------------------------")
|
||||||
|
@ -155,14 +154,22 @@ func fibTC2(n, a, b int) int {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func runBench(input []byte) (parseTime time.Duration, compileTime time.Duration, runTime time.Duration, result objects.Object, err error) {
|
func runBench(
|
||||||
var astFile *ast.File
|
input []byte,
|
||||||
|
) (
|
||||||
|
parseTime time.Duration,
|
||||||
|
compileTime time.Duration,
|
||||||
|
runTime time.Duration,
|
||||||
|
result tengo.Object,
|
||||||
|
err error,
|
||||||
|
) {
|
||||||
|
var astFile *internal.File
|
||||||
parseTime, astFile, err = parse(input)
|
parseTime, astFile, err = parse(input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var bytecode *compiler.Bytecode
|
var bytecode *tengo.Bytecode
|
||||||
compileTime, bytecode, err = compileFile(astFile)
|
compileTime, bytecode, err = compileFile(astFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
@ -173,13 +180,13 @@ func runBench(input []byte) (parseTime time.Duration, compileTime time.Duration,
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func parse(input []byte) (time.Duration, *ast.File, error) {
|
func parse(input []byte) (time.Duration, *internal.File, error) {
|
||||||
fileSet := source.NewFileSet()
|
fileSet := internal.NewFileSet()
|
||||||
inputFile := fileSet.AddFile("bench", -1, len(input))
|
inputFile := fileSet.AddFile("bench", -1, len(input))
|
||||||
|
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
p := parser.NewParser(inputFile, input, nil)
|
p := internal.NewParser(inputFile, input, nil)
|
||||||
file, err := p.ParseFile()
|
file, err := p.ParseFile()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return time.Since(start), nil, err
|
return time.Since(start), nil, err
|
||||||
|
@ -188,13 +195,13 @@ func parse(input []byte) (time.Duration, *ast.File, error) {
|
||||||
return time.Since(start), file, nil
|
return time.Since(start), file, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func compileFile(file *ast.File) (time.Duration, *compiler.Bytecode, error) {
|
func compileFile(file *internal.File) (time.Duration, *tengo.Bytecode, error) {
|
||||||
symTable := compiler.NewSymbolTable()
|
symTable := internal.NewSymbolTable()
|
||||||
symTable.Define("out")
|
symTable.Define("out")
|
||||||
|
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
c := compiler.NewCompiler(file.InputFile, symTable, nil, nil, nil)
|
c := tengo.NewCompiler(file.InputFile, symTable, nil, nil, nil)
|
||||||
if err := c.Compile(file); err != nil {
|
if err := c.Compile(file); err != nil {
|
||||||
return time.Since(start), nil, err
|
return time.Since(start), nil, err
|
||||||
}
|
}
|
||||||
|
@ -205,12 +212,14 @@ func compileFile(file *ast.File) (time.Duration, *compiler.Bytecode, error) {
|
||||||
return time.Since(start), bytecode, nil
|
return time.Since(start), bytecode, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func runVM(bytecode *compiler.Bytecode) (time.Duration, objects.Object, error) {
|
func runVM(
|
||||||
globals := make([]objects.Object, runtime.GlobalsSize)
|
bytecode *tengo.Bytecode,
|
||||||
|
) (time.Duration, tengo.Object, error) {
|
||||||
|
globals := make([]tengo.Object, tengo.GlobalsSize)
|
||||||
|
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
v := runtime.NewVM(bytecode, globals, -1)
|
v := tengo.NewVM(bytecode, globals, -1)
|
||||||
if err := v.Run(); err != nil {
|
if err := v.Run(); err != nil {
|
||||||
return time.Since(start), nil, err
|
return time.Since(start), nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,26 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
"flag"
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/d5/tengo/cli"
|
"github.com/d5/tengo"
|
||||||
|
"github.com/d5/tengo/internal"
|
||||||
"github.com/d5/tengo/stdlib"
|
"github.com/d5/tengo/stdlib"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
sourceFileExt = ".tengo"
|
||||||
|
replPrompt = ">> "
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
compileOutput string
|
compileOutput string
|
||||||
showHelp bool
|
showHelp bool
|
||||||
|
@ -22,12 +36,274 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
cli.Run(&cli.Options{
|
if showHelp {
|
||||||
ShowHelp: showHelp,
|
doHelp()
|
||||||
ShowVersion: showVersion,
|
os.Exit(2)
|
||||||
Version: version,
|
} else if showVersion {
|
||||||
CompileOutput: compileOutput,
|
fmt.Println(version)
|
||||||
Modules: stdlib.GetModuleMap(stdlib.AllModuleNames()...),
|
return
|
||||||
InputFile: flag.Arg(0),
|
}
|
||||||
})
|
|
||||||
|
modules := stdlib.GetModuleMap(stdlib.AllModuleNames()...)
|
||||||
|
inputFile := flag.Arg(0)
|
||||||
|
if inputFile == "" {
|
||||||
|
// REPL
|
||||||
|
RunREPL(modules, os.Stdin, os.Stdout)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
inputData, err := ioutil.ReadFile(inputFile)
|
||||||
|
if err != nil {
|
||||||
|
_, _ = fmt.Fprintf(os.Stderr,
|
||||||
|
"Error reading input file: %s", err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if compileOutput != "" {
|
||||||
|
err := CompileOnly(modules, inputData, inputFile,
|
||||||
|
compileOutput)
|
||||||
|
if err != nil {
|
||||||
|
_, _ = fmt.Fprintln(os.Stderr, err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
} else if filepath.Ext(inputFile) == sourceFileExt {
|
||||||
|
err := CompileAndRun(modules, inputData, inputFile)
|
||||||
|
if err != nil {
|
||||||
|
_, _ = fmt.Fprintln(os.Stderr, err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := RunCompiled(modules, inputData); err != nil {
|
||||||
|
_, _ = fmt.Fprintln(os.Stderr, err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompileOnly compiles the source code and writes the compiled binary into
|
||||||
|
// outputFile.
|
||||||
|
func CompileOnly(
|
||||||
|
modules *tengo.ModuleMap,
|
||||||
|
data []byte,
|
||||||
|
inputFile, outputFile string,
|
||||||
|
) (err error) {
|
||||||
|
bytecode, err := compileSrc(modules, data, filepath.Base(inputFile))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if outputFile == "" {
|
||||||
|
outputFile = basename(inputFile) + ".out"
|
||||||
|
}
|
||||||
|
|
||||||
|
out, err := os.OpenFile(outputFile, os.O_CREATE|os.O_WRONLY, os.ModePerm)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
_ = out.Close()
|
||||||
|
} else {
|
||||||
|
err = out.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
err = bytecode.Encode(out)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println(outputFile)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompileAndRun compiles the source code and executes it.
|
||||||
|
func CompileAndRun(
|
||||||
|
modules *tengo.ModuleMap,
|
||||||
|
data []byte,
|
||||||
|
inputFile string,
|
||||||
|
) (err error) {
|
||||||
|
bytecode, err := compileSrc(modules, data, filepath.Base(inputFile))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
machine := tengo.NewVM(bytecode, nil, -1)
|
||||||
|
err = machine.Run()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunCompiled reads the compiled binary from file and executes it.
|
||||||
|
func RunCompiled(modules *tengo.ModuleMap, data []byte) (err error) {
|
||||||
|
bytecode := &tengo.Bytecode{}
|
||||||
|
err = bytecode.Decode(bytes.NewReader(data), modules)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
machine := tengo.NewVM(bytecode, nil, -1)
|
||||||
|
err = machine.Run()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunREPL starts REPL.
|
||||||
|
func RunREPL(modules *tengo.ModuleMap, in io.Reader, out io.Writer) {
|
||||||
|
stdin := bufio.NewScanner(in)
|
||||||
|
fileSet := internal.NewFileSet()
|
||||||
|
globals := make([]tengo.Object, tengo.GlobalsSize)
|
||||||
|
symbolTable := internal.NewSymbolTable()
|
||||||
|
for idx, fn := range tengo.GetAllBuiltinFunctions() {
|
||||||
|
symbolTable.DefineBuiltin(idx, fn.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// embed println function
|
||||||
|
symbol := symbolTable.Define("__repl_println__")
|
||||||
|
globals[symbol.Index] = &tengo.UserFunction{
|
||||||
|
Name: "println",
|
||||||
|
Value: func(args ...tengo.Object) (ret tengo.Object, err error) {
|
||||||
|
var printArgs []interface{}
|
||||||
|
for _, arg := range args {
|
||||||
|
if _, isUndefined := arg.(*tengo.Undefined); isUndefined {
|
||||||
|
printArgs = append(printArgs, "<undefined>")
|
||||||
|
} else {
|
||||||
|
s, _ := tengo.ToString(arg)
|
||||||
|
printArgs = append(printArgs, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
printArgs = append(printArgs, "\n")
|
||||||
|
_, _ = fmt.Print(printArgs...)
|
||||||
|
return
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var constants []tengo.Object
|
||||||
|
for {
|
||||||
|
_, _ = fmt.Fprint(out, replPrompt)
|
||||||
|
scanned := stdin.Scan()
|
||||||
|
if !scanned {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
line := stdin.Text()
|
||||||
|
srcFile := fileSet.AddFile("repl", -1, len(line))
|
||||||
|
p := internal.NewParser(srcFile, []byte(line), nil)
|
||||||
|
file, err := p.ParseFile()
|
||||||
|
if err != nil {
|
||||||
|
_, _ = fmt.Fprintln(out, err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
file = addPrints(file)
|
||||||
|
c := tengo.NewCompiler(srcFile, symbolTable, constants, modules, nil)
|
||||||
|
if err := c.Compile(file); err != nil {
|
||||||
|
_, _ = fmt.Fprintln(out, err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
bytecode := c.Bytecode()
|
||||||
|
machine := tengo.NewVM(bytecode, globals, -1)
|
||||||
|
if err := machine.Run(); err != nil {
|
||||||
|
_, _ = fmt.Fprintln(out, err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
constants = bytecode.Constants
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func compileSrc(
|
||||||
|
modules *tengo.ModuleMap,
|
||||||
|
src []byte,
|
||||||
|
filename string,
|
||||||
|
) (*tengo.Bytecode, error) {
|
||||||
|
fileSet := internal.NewFileSet()
|
||||||
|
srcFile := fileSet.AddFile(filename, -1, len(src))
|
||||||
|
|
||||||
|
p := internal.NewParser(srcFile, src, nil)
|
||||||
|
file, err := p.ParseFile()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
c := tengo.NewCompiler(srcFile, nil, nil, modules, nil)
|
||||||
|
c.EnableFileImport(true)
|
||||||
|
|
||||||
|
if err := c.Compile(file); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
bytecode := c.Bytecode()
|
||||||
|
bytecode.RemoveDuplicates()
|
||||||
|
return bytecode, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func doHelp() {
|
||||||
|
fmt.Println("Usage:")
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println(" tengo [flags] {input-file}")
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("Flags:")
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println(" -o compile output file")
|
||||||
|
fmt.Println(" -version show version")
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("Examples:")
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println(" tengo")
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println(" Start Tengo REPL")
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println(" tengo myapp.tengo")
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println(" Compile and run source file (myapp.tengo)")
|
||||||
|
fmt.Println(" Source file must have .tengo extension")
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println(" tengo -o myapp myapp.tengo")
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println(" Compile source file (myapp.tengo) into bytecode file (myapp)")
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println(" tengo myapp")
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println(" Run bytecode file (myapp)")
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
|
||||||
|
func addPrints(file *internal.File) *internal.File {
|
||||||
|
var stmts []internal.Stmt
|
||||||
|
for _, s := range file.Stmts {
|
||||||
|
switch s := s.(type) {
|
||||||
|
case *internal.ExprStmt:
|
||||||
|
stmts = append(stmts, &internal.ExprStmt{
|
||||||
|
Expr: &internal.CallExpr{
|
||||||
|
Func: &internal.Ident{Name: "__repl_println__"},
|
||||||
|
Args: []internal.Expr{s.Expr},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
case *internal.AssignStmt:
|
||||||
|
stmts = append(stmts, s)
|
||||||
|
|
||||||
|
stmts = append(stmts, &internal.ExprStmt{
|
||||||
|
Expr: &internal.CallExpr{
|
||||||
|
Func: &internal.Ident{
|
||||||
|
Name: "__repl_println__",
|
||||||
|
},
|
||||||
|
Args: s.LHS,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
default:
|
||||||
|
stmts = append(stmts, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &internal.File{
|
||||||
|
InputFile: file.InputFile,
|
||||||
|
Stmts: stmts,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func basename(s string) string {
|
||||||
|
s = filepath.Base(s)
|
||||||
|
n := strings.LastIndexByte(s, '.')
|
||||||
|
if n > 0 {
|
||||||
|
return s[:n]
|
||||||
|
}
|
||||||
|
return s
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,31 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
|
|
||||||
"github.com/d5/tengo/cli"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
compileOutput string
|
|
||||||
showHelp bool
|
|
||||||
showVersion bool
|
|
||||||
version = "dev"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
flag.BoolVar(&showHelp, "help", false, "Show help")
|
|
||||||
flag.StringVar(&compileOutput, "o", "", "Compile output file")
|
|
||||||
flag.BoolVar(&showVersion, "version", false, "Show version")
|
|
||||||
flag.Parse()
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
cli.Run(&cli.Options{
|
|
||||||
ShowHelp: showHelp,
|
|
||||||
ShowVersion: showVersion,
|
|
||||||
Version: version,
|
|
||||||
CompileOutput: compileOutput,
|
|
||||||
InputFile: flag.Arg(0),
|
|
||||||
})
|
|
||||||
}
|
|
1285
compiler.go
Normal file
1285
compiler.go
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,35 +0,0 @@
|
||||||
package ast
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/d5/tengo/compiler/source"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ArrayLit represents an array literal.
|
|
||||||
type ArrayLit struct {
|
|
||||||
Elements []Expr
|
|
||||||
LBrack source.Pos
|
|
||||||
RBrack source.Pos
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *ArrayLit) exprNode() {}
|
|
||||||
|
|
||||||
// Pos returns the position of first character belonging to the node.
|
|
||||||
func (e *ArrayLit) Pos() source.Pos {
|
|
||||||
return e.LBrack
|
|
||||||
}
|
|
||||||
|
|
||||||
// End returns the position of first character immediately after the node.
|
|
||||||
func (e *ArrayLit) End() source.Pos {
|
|
||||||
return e.RBrack + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *ArrayLit) String() string {
|
|
||||||
var elements []string
|
|
||||||
for _, m := range e.Elements {
|
|
||||||
elements = append(elements, m.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
return "[" + strings.Join(elements, ", ") + "]"
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
package ast
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/d5/tengo/compiler/source"
|
|
||||||
"github.com/d5/tengo/compiler/token"
|
|
||||||
)
|
|
||||||
|
|
||||||
// AssignStmt represents an assignment statement.
|
|
||||||
type AssignStmt struct {
|
|
||||||
LHS []Expr
|
|
||||||
RHS []Expr
|
|
||||||
Token token.Token
|
|
||||||
TokenPos source.Pos
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *AssignStmt) stmtNode() {}
|
|
||||||
|
|
||||||
// Pos returns the position of first character belonging to the node.
|
|
||||||
func (s *AssignStmt) Pos() source.Pos {
|
|
||||||
return s.LHS[0].Pos()
|
|
||||||
}
|
|
||||||
|
|
||||||
// End returns the position of first character immediately after the node.
|
|
||||||
func (s *AssignStmt) End() source.Pos {
|
|
||||||
return s.RHS[len(s.RHS)-1].End()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *AssignStmt) String() string {
|
|
||||||
var lhs, rhs []string
|
|
||||||
for _, e := range s.LHS {
|
|
||||||
lhs = append(lhs, e.String())
|
|
||||||
}
|
|
||||||
for _, e := range s.RHS {
|
|
||||||
rhs = append(rhs, e.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.Join(lhs, ", ") + " " + s.Token.String() + " " + strings.Join(rhs, ", ")
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
package ast
|
|
||||||
|
|
||||||
const (
|
|
||||||
nullRep = "<null>"
|
|
||||||
)
|
|
|
@ -1,25 +0,0 @@
|
||||||
package ast
|
|
||||||
|
|
||||||
import "github.com/d5/tengo/compiler/source"
|
|
||||||
|
|
||||||
// BadExpr represents a bad expression.
|
|
||||||
type BadExpr struct {
|
|
||||||
From source.Pos
|
|
||||||
To source.Pos
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *BadExpr) exprNode() {}
|
|
||||||
|
|
||||||
// Pos returns the position of first character belonging to the node.
|
|
||||||
func (e *BadExpr) Pos() source.Pos {
|
|
||||||
return e.From
|
|
||||||
}
|
|
||||||
|
|
||||||
// End returns the position of first character immediately after the node.
|
|
||||||
func (e *BadExpr) End() source.Pos {
|
|
||||||
return e.To
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *BadExpr) String() string {
|
|
||||||
return "<bad expression>"
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
package ast
|
|
||||||
|
|
||||||
import "github.com/d5/tengo/compiler/source"
|
|
||||||
|
|
||||||
// BadStmt represents a bad statement.
|
|
||||||
type BadStmt struct {
|
|
||||||
From source.Pos
|
|
||||||
To source.Pos
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *BadStmt) stmtNode() {}
|
|
||||||
|
|
||||||
// Pos returns the position of first character belonging to the node.
|
|
||||||
func (s *BadStmt) Pos() source.Pos {
|
|
||||||
return s.From
|
|
||||||
}
|
|
||||||
|
|
||||||
// End returns the position of first character immediately after the node.
|
|
||||||
func (s *BadStmt) End() source.Pos {
|
|
||||||
return s.To
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *BadStmt) String() string {
|
|
||||||
return "<bad statement>"
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
package ast
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/d5/tengo/compiler/source"
|
|
||||||
"github.com/d5/tengo/compiler/token"
|
|
||||||
)
|
|
||||||
|
|
||||||
// BinaryExpr represents a binary operator expression.
|
|
||||||
type BinaryExpr struct {
|
|
||||||
LHS Expr
|
|
||||||
RHS Expr
|
|
||||||
Token token.Token
|
|
||||||
TokenPos source.Pos
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *BinaryExpr) exprNode() {}
|
|
||||||
|
|
||||||
// Pos returns the position of first character belonging to the node.
|
|
||||||
func (e *BinaryExpr) Pos() source.Pos {
|
|
||||||
return e.LHS.Pos()
|
|
||||||
}
|
|
||||||
|
|
||||||
// End returns the position of first character immediately after the node.
|
|
||||||
func (e *BinaryExpr) End() source.Pos {
|
|
||||||
return e.RHS.End()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *BinaryExpr) String() string {
|
|
||||||
return "(" + e.LHS.String() + " " + e.Token.String() + " " + e.RHS.String() + ")"
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
package ast
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/d5/tengo/compiler/source"
|
|
||||||
)
|
|
||||||
|
|
||||||
// BlockStmt represents a block statement.
|
|
||||||
type BlockStmt struct {
|
|
||||||
Stmts []Stmt
|
|
||||||
LBrace source.Pos
|
|
||||||
RBrace source.Pos
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *BlockStmt) stmtNode() {}
|
|
||||||
|
|
||||||
// Pos returns the position of first character belonging to the node.
|
|
||||||
func (s *BlockStmt) Pos() source.Pos {
|
|
||||||
return s.LBrace
|
|
||||||
}
|
|
||||||
|
|
||||||
// End returns the position of first character immediately after the node.
|
|
||||||
func (s *BlockStmt) End() source.Pos {
|
|
||||||
return s.RBrace + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *BlockStmt) String() string {
|
|
||||||
var list []string
|
|
||||||
for _, e := range s.Stmts {
|
|
||||||
list = append(list, e.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
return "{" + strings.Join(list, "; ") + "}"
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
package ast
|
|
||||||
|
|
||||||
import "github.com/d5/tengo/compiler/source"
|
|
||||||
|
|
||||||
// BoolLit represents a boolean literal.
|
|
||||||
type BoolLit struct {
|
|
||||||
Value bool
|
|
||||||
ValuePos source.Pos
|
|
||||||
Literal string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *BoolLit) exprNode() {}
|
|
||||||
|
|
||||||
// Pos returns the position of first character belonging to the node.
|
|
||||||
func (e *BoolLit) Pos() source.Pos {
|
|
||||||
return e.ValuePos
|
|
||||||
}
|
|
||||||
|
|
||||||
// End returns the position of first character immediately after the node.
|
|
||||||
func (e *BoolLit) End() source.Pos {
|
|
||||||
return source.Pos(int(e.ValuePos) + len(e.Literal))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *BoolLit) String() string {
|
|
||||||
return e.Literal
|
|
||||||
}
|
|
|
@ -1,38 +0,0 @@
|
||||||
package ast
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/d5/tengo/compiler/source"
|
|
||||||
"github.com/d5/tengo/compiler/token"
|
|
||||||
)
|
|
||||||
|
|
||||||
// BranchStmt represents a branch statement.
|
|
||||||
type BranchStmt struct {
|
|
||||||
Token token.Token
|
|
||||||
TokenPos source.Pos
|
|
||||||
Label *Ident
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *BranchStmt) stmtNode() {}
|
|
||||||
|
|
||||||
// Pos returns the position of first character belonging to the node.
|
|
||||||
func (s *BranchStmt) Pos() source.Pos {
|
|
||||||
return s.TokenPos
|
|
||||||
}
|
|
||||||
|
|
||||||
// End returns the position of first character immediately after the node.
|
|
||||||
func (s *BranchStmt) End() source.Pos {
|
|
||||||
if s.Label != nil {
|
|
||||||
return s.Label.End()
|
|
||||||
}
|
|
||||||
|
|
||||||
return source.Pos(int(s.TokenPos) + len(s.Token.String()))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *BranchStmt) String() string {
|
|
||||||
var label string
|
|
||||||
if s.Label != nil {
|
|
||||||
label = " " + s.Label.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.Token.String() + label
|
|
||||||
}
|
|
|
@ -1,36 +0,0 @@
|
||||||
package ast
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/d5/tengo/compiler/source"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CallExpr represents a function call expression.
|
|
||||||
type CallExpr struct {
|
|
||||||
Func Expr
|
|
||||||
LParen source.Pos
|
|
||||||
Args []Expr
|
|
||||||
RParen source.Pos
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *CallExpr) exprNode() {}
|
|
||||||
|
|
||||||
// Pos returns the position of first character belonging to the node.
|
|
||||||
func (e *CallExpr) Pos() source.Pos {
|
|
||||||
return e.Func.Pos()
|
|
||||||
}
|
|
||||||
|
|
||||||
// End returns the position of first character immediately after the node.
|
|
||||||
func (e *CallExpr) End() source.Pos {
|
|
||||||
return e.RParen + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *CallExpr) String() string {
|
|
||||||
var args []string
|
|
||||||
for _, e := range e.Args {
|
|
||||||
args = append(args, e.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
return e.Func.String() + "(" + strings.Join(args, ", ") + ")"
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
package ast
|
|
||||||
|
|
||||||
import "github.com/d5/tengo/compiler/source"
|
|
||||||
|
|
||||||
// CharLit represents a character literal.
|
|
||||||
type CharLit struct {
|
|
||||||
Value rune
|
|
||||||
ValuePos source.Pos
|
|
||||||
Literal string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *CharLit) exprNode() {}
|
|
||||||
|
|
||||||
// Pos returns the position of first character belonging to the node.
|
|
||||||
func (e *CharLit) Pos() source.Pos {
|
|
||||||
return e.ValuePos
|
|
||||||
}
|
|
||||||
|
|
||||||
// End returns the position of first character immediately after the node.
|
|
||||||
func (e *CharLit) End() source.Pos {
|
|
||||||
return source.Pos(int(e.ValuePos) + len(e.Literal))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *CharLit) String() string {
|
|
||||||
return e.Literal
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
package ast
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/d5/tengo/compiler/source"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CondExpr represents a ternary conditional expression.
|
|
||||||
type CondExpr struct {
|
|
||||||
Cond Expr
|
|
||||||
True Expr
|
|
||||||
False Expr
|
|
||||||
QuestionPos source.Pos
|
|
||||||
ColonPos source.Pos
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *CondExpr) exprNode() {}
|
|
||||||
|
|
||||||
// Pos returns the position of first character belonging to the node.
|
|
||||||
func (e *CondExpr) Pos() source.Pos {
|
|
||||||
return e.Cond.Pos()
|
|
||||||
}
|
|
||||||
|
|
||||||
// End returns the position of first character immediately after the node.
|
|
||||||
func (e *CondExpr) End() source.Pos {
|
|
||||||
return e.False.End()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *CondExpr) String() string {
|
|
||||||
return "(" + e.Cond.String() + " ? " + e.True.String() + " : " + e.False.String() + ")"
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
package ast
|
|
||||||
|
|
||||||
import "github.com/d5/tengo/compiler/source"
|
|
||||||
|
|
||||||
// EmptyStmt represents an empty statement.
|
|
||||||
type EmptyStmt struct {
|
|
||||||
Semicolon source.Pos
|
|
||||||
Implicit bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *EmptyStmt) stmtNode() {}
|
|
||||||
|
|
||||||
// Pos returns the position of first character belonging to the node.
|
|
||||||
func (s *EmptyStmt) Pos() source.Pos {
|
|
||||||
return s.Semicolon
|
|
||||||
}
|
|
||||||
|
|
||||||
// End returns the position of first character immediately after the node.
|
|
||||||
func (s *EmptyStmt) End() source.Pos {
|
|
||||||
if s.Implicit {
|
|
||||||
return s.Semicolon
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.Semicolon + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *EmptyStmt) String() string {
|
|
||||||
return ";"
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
package ast
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/d5/tengo/compiler/source"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ErrorExpr represents an error expression
|
|
||||||
type ErrorExpr struct {
|
|
||||||
Expr Expr
|
|
||||||
ErrorPos source.Pos
|
|
||||||
LParen source.Pos
|
|
||||||
RParen source.Pos
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *ErrorExpr) exprNode() {}
|
|
||||||
|
|
||||||
// Pos returns the position of first character belonging to the node.
|
|
||||||
func (e *ErrorExpr) Pos() source.Pos {
|
|
||||||
return e.ErrorPos
|
|
||||||
}
|
|
||||||
|
|
||||||
// End returns the position of first character immediately after the node.
|
|
||||||
func (e *ErrorExpr) End() source.Pos {
|
|
||||||
return e.RParen
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *ErrorExpr) String() string {
|
|
||||||
return "error(" + e.Expr.String() + ")"
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
package ast
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/d5/tengo/compiler/source"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ExportStmt represents an export statement.
|
|
||||||
type ExportStmt struct {
|
|
||||||
ExportPos source.Pos
|
|
||||||
Result Expr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ExportStmt) stmtNode() {}
|
|
||||||
|
|
||||||
// Pos returns the position of first character belonging to the node.
|
|
||||||
func (s *ExportStmt) Pos() source.Pos {
|
|
||||||
return s.ExportPos
|
|
||||||
}
|
|
||||||
|
|
||||||
// End returns the position of first character immediately after the node.
|
|
||||||
func (s *ExportStmt) End() source.Pos {
|
|
||||||
return s.Result.End()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ExportStmt) String() string {
|
|
||||||
return "export " + s.Result.String()
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
package ast
|
|
||||||
|
|
||||||
// Expr represents an expression node in the AST.
|
|
||||||
type Expr interface {
|
|
||||||
Node
|
|
||||||
exprNode()
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
package ast
|
|
||||||
|
|
||||||
import "github.com/d5/tengo/compiler/source"
|
|
||||||
|
|
||||||
// ExprStmt represents an expression statement.
|
|
||||||
type ExprStmt struct {
|
|
||||||
Expr Expr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ExprStmt) stmtNode() {}
|
|
||||||
|
|
||||||
// Pos returns the position of first character belonging to the node.
|
|
||||||
func (s *ExprStmt) Pos() source.Pos {
|
|
||||||
return s.Expr.Pos()
|
|
||||||
}
|
|
||||||
|
|
||||||
// End returns the position of first character immediately after the node.
|
|
||||||
func (s *ExprStmt) End() source.Pos {
|
|
||||||
return s.Expr.End()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ExprStmt) String() string {
|
|
||||||
return s.Expr.String()
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
package ast
|
|
||||||
|
|
||||||
import "github.com/d5/tengo/compiler/source"
|
|
||||||
|
|
||||||
// FloatLit represents a floating point literal.
|
|
||||||
type FloatLit struct {
|
|
||||||
Value float64
|
|
||||||
ValuePos source.Pos
|
|
||||||
Literal string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *FloatLit) exprNode() {}
|
|
||||||
|
|
||||||
// Pos returns the position of first character belonging to the node.
|
|
||||||
func (e *FloatLit) Pos() source.Pos {
|
|
||||||
return e.ValuePos
|
|
||||||
}
|
|
||||||
|
|
||||||
// End returns the position of first character immediately after the node.
|
|
||||||
func (e *FloatLit) End() source.Pos {
|
|
||||||
return source.Pos(int(e.ValuePos) + len(e.Literal))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *FloatLit) String() string {
|
|
||||||
return e.Literal
|
|
||||||
}
|
|
|
@ -1,32 +0,0 @@
|
||||||
package ast
|
|
||||||
|
|
||||||
import "github.com/d5/tengo/compiler/source"
|
|
||||||
|
|
||||||
// ForInStmt represents a for-in statement.
|
|
||||||
type ForInStmt struct {
|
|
||||||
ForPos source.Pos
|
|
||||||
Key *Ident
|
|
||||||
Value *Ident
|
|
||||||
Iterable Expr
|
|
||||||
Body *BlockStmt
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ForInStmt) stmtNode() {}
|
|
||||||
|
|
||||||
// Pos returns the position of first character belonging to the node.
|
|
||||||
func (s *ForInStmt) Pos() source.Pos {
|
|
||||||
return s.ForPos
|
|
||||||
}
|
|
||||||
|
|
||||||
// End returns the position of first character immediately after the node.
|
|
||||||
func (s *ForInStmt) End() source.Pos {
|
|
||||||
return s.Body.End()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ForInStmt) String() string {
|
|
||||||
if s.Value != nil {
|
|
||||||
return "for " + s.Key.String() + ", " + s.Value.String() + " in " + s.Iterable.String() + " " + s.Body.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
return "for " + s.Key.String() + " in " + s.Iterable.String() + " " + s.Body.String()
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
package ast
|
|
||||||
|
|
||||||
import "github.com/d5/tengo/compiler/source"
|
|
||||||
|
|
||||||
// ForStmt represents a for statement.
|
|
||||||
type ForStmt struct {
|
|
||||||
ForPos source.Pos
|
|
||||||
Init Stmt
|
|
||||||
Cond Expr
|
|
||||||
Post Stmt
|
|
||||||
Body *BlockStmt
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ForStmt) stmtNode() {}
|
|
||||||
|
|
||||||
// Pos returns the position of first character belonging to the node.
|
|
||||||
func (s *ForStmt) Pos() source.Pos {
|
|
||||||
return s.ForPos
|
|
||||||
}
|
|
||||||
|
|
||||||
// End returns the position of first character immediately after the node.
|
|
||||||
func (s *ForStmt) End() source.Pos {
|
|
||||||
return s.Body.End()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ForStmt) String() string {
|
|
||||||
var init, cond, post string
|
|
||||||
if s.Init != nil {
|
|
||||||
init = s.Init.String()
|
|
||||||
}
|
|
||||||
if s.Cond != nil {
|
|
||||||
cond = s.Cond.String() + " "
|
|
||||||
}
|
|
||||||
if s.Post != nil {
|
|
||||||
post = s.Post.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
if init != "" || post != "" {
|
|
||||||
return "for " + init + " ; " + cond + " ; " + post + s.Body.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
return "for " + cond + s.Body.String()
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
package ast
|
|
||||||
|
|
||||||
import "github.com/d5/tengo/compiler/source"
|
|
||||||
|
|
||||||
// FuncLit represents a function literal.
|
|
||||||
type FuncLit struct {
|
|
||||||
Type *FuncType
|
|
||||||
Body *BlockStmt
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *FuncLit) exprNode() {}
|
|
||||||
|
|
||||||
// Pos returns the position of first character belonging to the node.
|
|
||||||
func (e *FuncLit) Pos() source.Pos {
|
|
||||||
return e.Type.Pos()
|
|
||||||
}
|
|
||||||
|
|
||||||
// End returns the position of first character immediately after the node.
|
|
||||||
func (e *FuncLit) End() source.Pos {
|
|
||||||
return e.Body.End()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *FuncLit) String() string {
|
|
||||||
return "func" + e.Type.Params.String() + " " + e.Body.String()
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
package ast
|
|
||||||
|
|
||||||
import "github.com/d5/tengo/compiler/source"
|
|
||||||
|
|
||||||
// FuncType represents a function type definition.
|
|
||||||
type FuncType struct {
|
|
||||||
FuncPos source.Pos
|
|
||||||
Params *IdentList
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *FuncType) exprNode() {}
|
|
||||||
|
|
||||||
// Pos returns the position of first character belonging to the node.
|
|
||||||
func (e *FuncType) Pos() source.Pos {
|
|
||||||
return e.FuncPos
|
|
||||||
}
|
|
||||||
|
|
||||||
// End returns the position of first character immediately after the node.
|
|
||||||
func (e *FuncType) End() source.Pos {
|
|
||||||
return e.Params.End()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *FuncType) String() string {
|
|
||||||
return "func" + e.Params.String()
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
package ast
|
|
||||||
|
|
||||||
import "github.com/d5/tengo/compiler/source"
|
|
||||||
|
|
||||||
// Ident represents an identifier.
|
|
||||||
type Ident struct {
|
|
||||||
Name string
|
|
||||||
NamePos source.Pos
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Ident) exprNode() {}
|
|
||||||
|
|
||||||
// Pos returns the position of first character belonging to the node.
|
|
||||||
func (e *Ident) Pos() source.Pos {
|
|
||||||
return e.NamePos
|
|
||||||
}
|
|
||||||
|
|
||||||
// End returns the position of first character immediately after the node.
|
|
||||||
func (e *Ident) End() source.Pos {
|
|
||||||
return source.Pos(int(e.NamePos) + len(e.Name))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Ident) String() string {
|
|
||||||
if e != nil {
|
|
||||||
return e.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
return nullRep
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
package ast
|
|
||||||
|
|
||||||
import "github.com/d5/tengo/compiler/source"
|
|
||||||
|
|
||||||
// IfStmt represents an if statement.
|
|
||||||
type IfStmt struct {
|
|
||||||
IfPos source.Pos
|
|
||||||
Init Stmt
|
|
||||||
Cond Expr
|
|
||||||
Body *BlockStmt
|
|
||||||
Else Stmt // else branch; or nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *IfStmt) stmtNode() {}
|
|
||||||
|
|
||||||
// Pos returns the position of first character belonging to the node.
|
|
||||||
func (s *IfStmt) Pos() source.Pos {
|
|
||||||
return s.IfPos
|
|
||||||
}
|
|
||||||
|
|
||||||
// End returns the position of first character immediately after the node.
|
|
||||||
func (s *IfStmt) End() source.Pos {
|
|
||||||
if s.Else != nil {
|
|
||||||
return s.Else.End()
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.Body.End()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *IfStmt) String() string {
|
|
||||||
var initStmt, elseStmt string
|
|
||||||
if s.Init != nil {
|
|
||||||
initStmt = s.Init.String() + "; "
|
|
||||||
}
|
|
||||||
if s.Else != nil {
|
|
||||||
elseStmt = " else " + s.Else.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
return "if " + initStmt + s.Cond.String() + " " + s.Body.String() + elseStmt
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
package ast
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/d5/tengo/compiler/source"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ImmutableExpr represents an immutable expression
|
|
||||||
type ImmutableExpr struct {
|
|
||||||
Expr Expr
|
|
||||||
ErrorPos source.Pos
|
|
||||||
LParen source.Pos
|
|
||||||
RParen source.Pos
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *ImmutableExpr) exprNode() {}
|
|
||||||
|
|
||||||
// Pos returns the position of first character belonging to the node.
|
|
||||||
func (e *ImmutableExpr) Pos() source.Pos {
|
|
||||||
return e.ErrorPos
|
|
||||||
}
|
|
||||||
|
|
||||||
// End returns the position of first character immediately after the node.
|
|
||||||
func (e *ImmutableExpr) End() source.Pos {
|
|
||||||
return e.RParen
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *ImmutableExpr) String() string {
|
|
||||||
return "immutable(" + e.Expr.String() + ")"
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
package ast
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/d5/tengo/compiler/source"
|
|
||||||
"github.com/d5/tengo/compiler/token"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ImportExpr represents an import expression
|
|
||||||
type ImportExpr struct {
|
|
||||||
ModuleName string
|
|
||||||
Token token.Token
|
|
||||||
TokenPos source.Pos
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *ImportExpr) exprNode() {}
|
|
||||||
|
|
||||||
// Pos returns the position of first character belonging to the node.
|
|
||||||
func (e *ImportExpr) Pos() source.Pos {
|
|
||||||
return e.TokenPos
|
|
||||||
}
|
|
||||||
|
|
||||||
// End returns the position of first character immediately after the node.
|
|
||||||
func (e *ImportExpr) End() source.Pos {
|
|
||||||
return source.Pos(int(e.TokenPos) + 10 + len(e.ModuleName)) // import("moduleName")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *ImportExpr) String() string {
|
|
||||||
return `import("` + e.ModuleName + `")"`
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
package ast
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/d5/tengo/compiler/source"
|
|
||||||
"github.com/d5/tengo/compiler/token"
|
|
||||||
)
|
|
||||||
|
|
||||||
// IncDecStmt represents increment or decrement statement.
|
|
||||||
type IncDecStmt struct {
|
|
||||||
Expr Expr
|
|
||||||
Token token.Token
|
|
||||||
TokenPos source.Pos
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *IncDecStmt) stmtNode() {}
|
|
||||||
|
|
||||||
// Pos returns the position of first character belonging to the node.
|
|
||||||
func (s *IncDecStmt) Pos() source.Pos {
|
|
||||||
return s.Expr.Pos()
|
|
||||||
}
|
|
||||||
|
|
||||||
// End returns the position of first character immediately after the node.
|
|
||||||
func (s *IncDecStmt) End() source.Pos {
|
|
||||||
return source.Pos(int(s.TokenPos) + 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *IncDecStmt) String() string {
|
|
||||||
return s.Expr.String() + s.Token.String()
|
|
||||||
}
|
|
|
@ -1,32 +0,0 @@
|
||||||
package ast
|
|
||||||
|
|
||||||
import "github.com/d5/tengo/compiler/source"
|
|
||||||
|
|
||||||
// IndexExpr represents an index expression.
|
|
||||||
type IndexExpr struct {
|
|
||||||
Expr Expr
|
|
||||||
LBrack source.Pos
|
|
||||||
Index Expr
|
|
||||||
RBrack source.Pos
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *IndexExpr) exprNode() {}
|
|
||||||
|
|
||||||
// Pos returns the position of first character belonging to the node.
|
|
||||||
func (e *IndexExpr) Pos() source.Pos {
|
|
||||||
return e.Expr.Pos()
|
|
||||||
}
|
|
||||||
|
|
||||||
// End returns the position of first character immediately after the node.
|
|
||||||
func (e *IndexExpr) End() source.Pos {
|
|
||||||
return e.RBrack + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *IndexExpr) String() string {
|
|
||||||
var index string
|
|
||||||
if e.Index != nil {
|
|
||||||
index = e.Index.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
return e.Expr.String() + "[" + index + "]"
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
package ast
|
|
||||||
|
|
||||||
import "github.com/d5/tengo/compiler/source"
|
|
||||||
|
|
||||||
// IntLit represents an integer literal.
|
|
||||||
type IntLit struct {
|
|
||||||
Value int64
|
|
||||||
ValuePos source.Pos
|
|
||||||
Literal string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *IntLit) exprNode() {}
|
|
||||||
|
|
||||||
// Pos returns the position of first character belonging to the node.
|
|
||||||
func (e *IntLit) Pos() source.Pos {
|
|
||||||
return e.ValuePos
|
|
||||||
}
|
|
||||||
|
|
||||||
// End returns the position of first character immediately after the node.
|
|
||||||
func (e *IntLit) End() source.Pos {
|
|
||||||
return source.Pos(int(e.ValuePos) + len(e.Literal))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *IntLit) String() string {
|
|
||||||
return e.Literal
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
package ast
|
|
||||||
|
|
||||||
import "github.com/d5/tengo/compiler/source"
|
|
||||||
|
|
||||||
// MapElementLit represents a map element.
|
|
||||||
type MapElementLit struct {
|
|
||||||
Key string
|
|
||||||
KeyPos source.Pos
|
|
||||||
ColonPos source.Pos
|
|
||||||
Value Expr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *MapElementLit) exprNode() {}
|
|
||||||
|
|
||||||
// Pos returns the position of first character belonging to the node.
|
|
||||||
func (e *MapElementLit) Pos() source.Pos {
|
|
||||||
return e.KeyPos
|
|
||||||
}
|
|
||||||
|
|
||||||
// End returns the position of first character immediately after the node.
|
|
||||||
func (e *MapElementLit) End() source.Pos {
|
|
||||||
return e.Value.End()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *MapElementLit) String() string {
|
|
||||||
return e.Key + ": " + e.Value.String()
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
package ast
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/d5/tengo/compiler/source"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MapLit represents a map literal.
|
|
||||||
type MapLit struct {
|
|
||||||
LBrace source.Pos
|
|
||||||
Elements []*MapElementLit
|
|
||||||
RBrace source.Pos
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *MapLit) exprNode() {}
|
|
||||||
|
|
||||||
// Pos returns the position of first character belonging to the node.
|
|
||||||
func (e *MapLit) Pos() source.Pos {
|
|
||||||
return e.LBrace
|
|
||||||
}
|
|
||||||
|
|
||||||
// End returns the position of first character immediately after the node.
|
|
||||||
func (e *MapLit) End() source.Pos {
|
|
||||||
return e.RBrace + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *MapLit) String() string {
|
|
||||||
var elements []string
|
|
||||||
for _, m := range e.Elements {
|
|
||||||
elements = append(elements, m.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
return "{" + strings.Join(elements, ", ") + "}"
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
package ast
|
|
||||||
|
|
||||||
import "github.com/d5/tengo/compiler/source"
|
|
||||||
|
|
||||||
// Node represents a node in the AST.
|
|
||||||
type Node interface {
|
|
||||||
// Pos returns the position of first character belonging to the node.
|
|
||||||
Pos() source.Pos
|
|
||||||
// End returns the position of first character immediately after the node.
|
|
||||||
End() source.Pos
|
|
||||||
// String returns a string representation of the node.
|
|
||||||
String() string
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
package ast
|
|
||||||
|
|
||||||
import "github.com/d5/tengo/compiler/source"
|
|
||||||
|
|
||||||
// ParenExpr represents a parenthesis wrapped expression.
|
|
||||||
type ParenExpr struct {
|
|
||||||
Expr Expr
|
|
||||||
LParen source.Pos
|
|
||||||
RParen source.Pos
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *ParenExpr) exprNode() {}
|
|
||||||
|
|
||||||
// Pos returns the position of first character belonging to the node.
|
|
||||||
func (e *ParenExpr) Pos() source.Pos {
|
|
||||||
return e.LParen
|
|
||||||
}
|
|
||||||
|
|
||||||
// End returns the position of first character immediately after the node.
|
|
||||||
func (e *ParenExpr) End() source.Pos {
|
|
||||||
return e.RParen + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *ParenExpr) String() string {
|
|
||||||
return "(" + e.Expr.String() + ")"
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
package ast
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/d5/tengo/compiler/source"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ReturnStmt represents a return statement.
|
|
||||||
type ReturnStmt struct {
|
|
||||||
ReturnPos source.Pos
|
|
||||||
Result Expr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ReturnStmt) stmtNode() {}
|
|
||||||
|
|
||||||
// Pos returns the position of first character belonging to the node.
|
|
||||||
func (s *ReturnStmt) Pos() source.Pos {
|
|
||||||
return s.ReturnPos
|
|
||||||
}
|
|
||||||
|
|
||||||
// End returns the position of first character immediately after the node.
|
|
||||||
func (s *ReturnStmt) End() source.Pos {
|
|
||||||
if s.Result != nil {
|
|
||||||
return s.Result.End()
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.ReturnPos + 6
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ReturnStmt) String() string {
|
|
||||||
if s.Result != nil {
|
|
||||||
return "return " + s.Result.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
return "return"
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
package ast
|
|
||||||
|
|
||||||
import "github.com/d5/tengo/compiler/source"
|
|
||||||
|
|
||||||
// SelectorExpr represents a selector expression.
|
|
||||||
type SelectorExpr struct {
|
|
||||||
Expr Expr
|
|
||||||
Sel Expr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *SelectorExpr) exprNode() {}
|
|
||||||
|
|
||||||
// Pos returns the position of first character belonging to the node.
|
|
||||||
func (e *SelectorExpr) Pos() source.Pos {
|
|
||||||
return e.Expr.Pos()
|
|
||||||
}
|
|
||||||
|
|
||||||
// End returns the position of first character immediately after the node.
|
|
||||||
func (e *SelectorExpr) End() source.Pos {
|
|
||||||
return e.Sel.End()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *SelectorExpr) String() string {
|
|
||||||
return e.Expr.String() + "." + e.Sel.String()
|
|
||||||
}
|
|
|
@ -1,36 +0,0 @@
|
||||||
package ast
|
|
||||||
|
|
||||||
import "github.com/d5/tengo/compiler/source"
|
|
||||||
|
|
||||||
// SliceExpr represents a slice expression.
|
|
||||||
type SliceExpr struct {
|
|
||||||
Expr Expr
|
|
||||||
LBrack source.Pos
|
|
||||||
Low Expr
|
|
||||||
High Expr
|
|
||||||
RBrack source.Pos
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *SliceExpr) exprNode() {}
|
|
||||||
|
|
||||||
// Pos returns the position of first character belonging to the node.
|
|
||||||
func (e *SliceExpr) Pos() source.Pos {
|
|
||||||
return e.Expr.Pos()
|
|
||||||
}
|
|
||||||
|
|
||||||
// End returns the position of first character immediately after the node.
|
|
||||||
func (e *SliceExpr) End() source.Pos {
|
|
||||||
return e.RBrack + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *SliceExpr) String() string {
|
|
||||||
var low, high string
|
|
||||||
if e.Low != nil {
|
|
||||||
low = e.Low.String()
|
|
||||||
}
|
|
||||||
if e.High != nil {
|
|
||||||
high = e.High.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
return e.Expr.String() + "[" + low + ":" + high + "]"
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
package ast
|
|
||||||
|
|
||||||
// Stmt represents a statement in the AST.
|
|
||||||
type Stmt interface {
|
|
||||||
Node
|
|
||||||
stmtNode()
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
package ast
|
|
||||||
|
|
||||||
import "github.com/d5/tengo/compiler/source"
|
|
||||||
|
|
||||||
// StringLit represents a string literal.
|
|
||||||
type StringLit struct {
|
|
||||||
Value string
|
|
||||||
ValuePos source.Pos
|
|
||||||
Literal string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *StringLit) exprNode() {}
|
|
||||||
|
|
||||||
// Pos returns the position of first character belonging to the node.
|
|
||||||
func (e *StringLit) Pos() source.Pos {
|
|
||||||
return e.ValuePos
|
|
||||||
}
|
|
||||||
|
|
||||||
// End returns the position of first character immediately after the node.
|
|
||||||
func (e *StringLit) End() source.Pos {
|
|
||||||
return source.Pos(int(e.ValuePos) + len(e.Literal))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *StringLit) String() string {
|
|
||||||
return e.Literal
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
package ast
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/d5/tengo/compiler/source"
|
|
||||||
"github.com/d5/tengo/compiler/token"
|
|
||||||
)
|
|
||||||
|
|
||||||
// UnaryExpr represents an unary operator expression.
|
|
||||||
type UnaryExpr struct {
|
|
||||||
Expr Expr
|
|
||||||
Token token.Token
|
|
||||||
TokenPos source.Pos
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *UnaryExpr) exprNode() {}
|
|
||||||
|
|
||||||
// Pos returns the position of first character belonging to the node.
|
|
||||||
func (e *UnaryExpr) Pos() source.Pos {
|
|
||||||
return e.Expr.Pos()
|
|
||||||
}
|
|
||||||
|
|
||||||
// End returns the position of first character immediately after the node.
|
|
||||||
func (e *UnaryExpr) End() source.Pos {
|
|
||||||
return e.Expr.End()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *UnaryExpr) String() string {
|
|
||||||
return "(" + e.Token.String() + e.Expr.String() + ")"
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
package ast
|
|
||||||
|
|
||||||
import "github.com/d5/tengo/compiler/source"
|
|
||||||
|
|
||||||
// UndefinedLit represents an undefined literal.
|
|
||||||
type UndefinedLit struct {
|
|
||||||
TokenPos source.Pos
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *UndefinedLit) exprNode() {}
|
|
||||||
|
|
||||||
// Pos returns the position of first character belonging to the node.
|
|
||||||
func (e *UndefinedLit) Pos() source.Pos {
|
|
||||||
return e.TokenPos
|
|
||||||
}
|
|
||||||
|
|
||||||
// End returns the position of first character immediately after the node.
|
|
||||||
func (e *UndefinedLit) End() source.Pos {
|
|
||||||
return e.TokenPos + 9 // len(undefined) == 9
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *UndefinedLit) String() string {
|
|
||||||
return "undefined"
|
|
||||||
}
|
|
|
@ -1,90 +0,0 @@
|
||||||
package compiler
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/gob"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"reflect"
|
|
||||||
|
|
||||||
"github.com/d5/tengo/compiler/source"
|
|
||||||
"github.com/d5/tengo/objects"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Bytecode is a compiled instructions and constants.
|
|
||||||
type Bytecode struct {
|
|
||||||
FileSet *source.FileSet
|
|
||||||
MainFunction *objects.CompiledFunction
|
|
||||||
Constants []objects.Object
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encode writes Bytecode data to the writer.
|
|
||||||
func (b *Bytecode) Encode(w io.Writer) error {
|
|
||||||
enc := gob.NewEncoder(w)
|
|
||||||
|
|
||||||
if err := enc.Encode(b.FileSet); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := enc.Encode(b.MainFunction); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// constants
|
|
||||||
return enc.Encode(b.Constants)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CountObjects returns the number of objects found in Constants.
|
|
||||||
func (b *Bytecode) CountObjects() int {
|
|
||||||
n := 0
|
|
||||||
|
|
||||||
for _, c := range b.Constants {
|
|
||||||
n += objects.CountObjects(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
|
|
||||||
// FormatInstructions returns human readable string representations of
|
|
||||||
// compiled instructions.
|
|
||||||
func (b *Bytecode) FormatInstructions() []string {
|
|
||||||
return FormatInstructions(b.MainFunction.Instructions, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FormatConstants returns human readable string representations of
|
|
||||||
// compiled constants.
|
|
||||||
func (b *Bytecode) FormatConstants() (output []string) {
|
|
||||||
for cidx, cn := range b.Constants {
|
|
||||||
switch cn := cn.(type) {
|
|
||||||
case *objects.CompiledFunction:
|
|
||||||
output = append(output, fmt.Sprintf("[% 3d] (Compiled Function|%p)", cidx, &cn))
|
|
||||||
for _, l := range FormatInstructions(cn.Instructions, 0) {
|
|
||||||
output = append(output, fmt.Sprintf(" %s", l))
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
output = append(output, fmt.Sprintf("[% 3d] %s (%s|%p)", cidx, cn, reflect.TypeOf(cn).Elem().Name(), &cn))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
gob.Register(&source.FileSet{})
|
|
||||||
gob.Register(&source.File{})
|
|
||||||
gob.Register(&objects.Array{})
|
|
||||||
gob.Register(&objects.Bool{})
|
|
||||||
gob.Register(&objects.Bytes{})
|
|
||||||
gob.Register(&objects.Char{})
|
|
||||||
gob.Register(&objects.Closure{})
|
|
||||||
gob.Register(&objects.CompiledFunction{})
|
|
||||||
gob.Register(&objects.Error{})
|
|
||||||
gob.Register(&objects.Float{})
|
|
||||||
gob.Register(&objects.ImmutableArray{})
|
|
||||||
gob.Register(&objects.ImmutableMap{})
|
|
||||||
gob.Register(&objects.Int{})
|
|
||||||
gob.Register(&objects.Map{})
|
|
||||||
gob.Register(&objects.String{})
|
|
||||||
gob.Register(&objects.Time{})
|
|
||||||
gob.Register(&objects.Undefined{})
|
|
||||||
gob.Register(&objects.UserFunction{})
|
|
||||||
}
|
|
|
@ -1,97 +0,0 @@
|
||||||
package compiler
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/gob"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/d5/tengo/objects"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Decode reads Bytecode data from the reader.
|
|
||||||
func (b *Bytecode) Decode(r io.Reader, modules *objects.ModuleMap) error {
|
|
||||||
if modules == nil {
|
|
||||||
modules = objects.NewModuleMap()
|
|
||||||
}
|
|
||||||
|
|
||||||
dec := gob.NewDecoder(r)
|
|
||||||
|
|
||||||
if err := dec.Decode(&b.FileSet); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// TODO: files in b.FileSet.File does not have their 'set' field properly set to b.FileSet
|
|
||||||
// as it's private field and not serialized by gob encoder/decoder.
|
|
||||||
|
|
||||||
if err := dec.Decode(&b.MainFunction); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := dec.Decode(&b.Constants); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for i, v := range b.Constants {
|
|
||||||
fv, err := fixDecoded(v, modules)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
b.Constants[i] = fv
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func fixDecoded(o objects.Object, modules *objects.ModuleMap) (objects.Object, error) {
|
|
||||||
switch o := o.(type) {
|
|
||||||
case *objects.Bool:
|
|
||||||
if o.IsFalsy() {
|
|
||||||
return objects.FalseValue, nil
|
|
||||||
}
|
|
||||||
return objects.TrueValue, nil
|
|
||||||
case *objects.Undefined:
|
|
||||||
return objects.UndefinedValue, nil
|
|
||||||
case *objects.Array:
|
|
||||||
for i, v := range o.Value {
|
|
||||||
fv, err := fixDecoded(v, modules)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
o.Value[i] = fv
|
|
||||||
}
|
|
||||||
case *objects.ImmutableArray:
|
|
||||||
for i, v := range o.Value {
|
|
||||||
fv, err := fixDecoded(v, modules)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
o.Value[i] = fv
|
|
||||||
}
|
|
||||||
case *objects.Map:
|
|
||||||
for k, v := range o.Value {
|
|
||||||
fv, err := fixDecoded(v, modules)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
o.Value[k] = fv
|
|
||||||
}
|
|
||||||
case *objects.ImmutableMap:
|
|
||||||
modName := moduleName(o)
|
|
||||||
if mod := modules.GetBuiltinModule(modName); mod != nil {
|
|
||||||
return mod.AsImmutableMap(modName), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for k, v := range o.Value {
|
|
||||||
// encoding of user function not supported
|
|
||||||
if _, isUserFunction := v.(*objects.UserFunction); isUserFunction {
|
|
||||||
return nil, fmt.Errorf("user function not decodable")
|
|
||||||
}
|
|
||||||
|
|
||||||
fv, err := fixDecoded(v, modules)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
o.Value[k] = fv
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return o, nil
|
|
||||||
}
|
|
|
@ -1,129 +0,0 @@
|
||||||
package compiler
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/d5/tengo/objects"
|
|
||||||
)
|
|
||||||
|
|
||||||
// RemoveDuplicates finds and remove the duplicate values in Constants.
|
|
||||||
// Note this function mutates Bytecode.
|
|
||||||
func (b *Bytecode) RemoveDuplicates() {
|
|
||||||
var deduped []objects.Object
|
|
||||||
|
|
||||||
indexMap := make(map[int]int) // mapping from old constant index to new index
|
|
||||||
ints := make(map[int64]int)
|
|
||||||
strings := make(map[string]int)
|
|
||||||
floats := make(map[float64]int)
|
|
||||||
chars := make(map[rune]int)
|
|
||||||
immutableMaps := make(map[string]int) // for modules
|
|
||||||
|
|
||||||
for curIdx, c := range b.Constants {
|
|
||||||
switch c := c.(type) {
|
|
||||||
case *objects.CompiledFunction:
|
|
||||||
// add to deduped list
|
|
||||||
indexMap[curIdx] = len(deduped)
|
|
||||||
deduped = append(deduped, c)
|
|
||||||
case *objects.ImmutableMap:
|
|
||||||
modName := moduleName(c)
|
|
||||||
newIdx, ok := immutableMaps[modName]
|
|
||||||
if modName != "" && ok {
|
|
||||||
indexMap[curIdx] = newIdx
|
|
||||||
} else {
|
|
||||||
newIdx = len(deduped)
|
|
||||||
immutableMaps[modName] = newIdx
|
|
||||||
indexMap[curIdx] = newIdx
|
|
||||||
deduped = append(deduped, c)
|
|
||||||
}
|
|
||||||
case *objects.Int:
|
|
||||||
if newIdx, ok := ints[c.Value]; ok {
|
|
||||||
indexMap[curIdx] = newIdx
|
|
||||||
} else {
|
|
||||||
newIdx = len(deduped)
|
|
||||||
ints[c.Value] = newIdx
|
|
||||||
indexMap[curIdx] = newIdx
|
|
||||||
deduped = append(deduped, c)
|
|
||||||
}
|
|
||||||
case *objects.String:
|
|
||||||
if newIdx, ok := strings[c.Value]; ok {
|
|
||||||
indexMap[curIdx] = newIdx
|
|
||||||
} else {
|
|
||||||
newIdx = len(deduped)
|
|
||||||
strings[c.Value] = newIdx
|
|
||||||
indexMap[curIdx] = newIdx
|
|
||||||
deduped = append(deduped, c)
|
|
||||||
}
|
|
||||||
case *objects.Float:
|
|
||||||
if newIdx, ok := floats[c.Value]; ok {
|
|
||||||
indexMap[curIdx] = newIdx
|
|
||||||
} else {
|
|
||||||
newIdx = len(deduped)
|
|
||||||
floats[c.Value] = newIdx
|
|
||||||
indexMap[curIdx] = newIdx
|
|
||||||
deduped = append(deduped, c)
|
|
||||||
}
|
|
||||||
case *objects.Char:
|
|
||||||
if newIdx, ok := chars[c.Value]; ok {
|
|
||||||
indexMap[curIdx] = newIdx
|
|
||||||
} else {
|
|
||||||
newIdx = len(deduped)
|
|
||||||
chars[c.Value] = newIdx
|
|
||||||
indexMap[curIdx] = newIdx
|
|
||||||
deduped = append(deduped, c)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
panic(fmt.Errorf("unsupported top-level constant type: %s", c.TypeName()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// replace with de-duplicated constants
|
|
||||||
b.Constants = deduped
|
|
||||||
|
|
||||||
// update CONST instructions with new indexes
|
|
||||||
// main function
|
|
||||||
updateConstIndexes(b.MainFunction.Instructions, indexMap)
|
|
||||||
// other compiled functions in constants
|
|
||||||
for _, c := range b.Constants {
|
|
||||||
switch c := c.(type) {
|
|
||||||
case *objects.CompiledFunction:
|
|
||||||
updateConstIndexes(c.Instructions, indexMap)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateConstIndexes(insts []byte, indexMap map[int]int) {
|
|
||||||
i := 0
|
|
||||||
for i < len(insts) {
|
|
||||||
op := insts[i]
|
|
||||||
numOperands := OpcodeOperands[op]
|
|
||||||
_, read := ReadOperands(numOperands, insts[i+1:])
|
|
||||||
|
|
||||||
switch op {
|
|
||||||
case OpConstant:
|
|
||||||
curIdx := int(insts[i+2]) | int(insts[i+1])<<8
|
|
||||||
newIdx, ok := indexMap[curIdx]
|
|
||||||
if !ok {
|
|
||||||
panic(fmt.Errorf("constant index not found: %d", curIdx))
|
|
||||||
}
|
|
||||||
copy(insts[i:], MakeInstruction(op, newIdx))
|
|
||||||
case OpClosure:
|
|
||||||
curIdx := int(insts[i+2]) | int(insts[i+1])<<8
|
|
||||||
numFree := int(insts[i+3])
|
|
||||||
newIdx, ok := indexMap[curIdx]
|
|
||||||
if !ok {
|
|
||||||
panic(fmt.Errorf("constant index not found: %d", curIdx))
|
|
||||||
}
|
|
||||||
copy(insts[i:], MakeInstruction(op, newIdx, numFree))
|
|
||||||
}
|
|
||||||
|
|
||||||
i += 1 + read
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func moduleName(mod *objects.ImmutableMap) string {
|
|
||||||
if modName, ok := mod.Value["__module_name__"].(*objects.String); ok {
|
|
||||||
return modName.Value
|
|
||||||
}
|
|
||||||
|
|
||||||
return ""
|
|
||||||
}
|
|
|
@ -1,289 +0,0 @@
|
||||||
package compiler_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/d5/tengo/assert"
|
|
||||||
"github.com/d5/tengo/compiler"
|
|
||||||
"github.com/d5/tengo/compiler/source"
|
|
||||||
"github.com/d5/tengo/objects"
|
|
||||||
)
|
|
||||||
|
|
||||||
type srcfile struct {
|
|
||||||
name string
|
|
||||||
size int
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBytecode(t *testing.T) {
|
|
||||||
testBytecodeSerialization(t, bytecode(concat(), objectsArray()))
|
|
||||||
|
|
||||||
testBytecodeSerialization(t, bytecode(
|
|
||||||
concat(), objectsArray(
|
|
||||||
&objects.Char{Value: 'y'},
|
|
||||||
&objects.Float{Value: 93.11},
|
|
||||||
compiledFunction(1, 0,
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 3),
|
|
||||||
compiler.MakeInstruction(compiler.OpSetLocal, 0),
|
|
||||||
compiler.MakeInstruction(compiler.OpGetGlobal, 0),
|
|
||||||
compiler.MakeInstruction(compiler.OpGetFree, 0)),
|
|
||||||
&objects.Float{Value: 39.2},
|
|
||||||
&objects.Int{Value: 192},
|
|
||||||
&objects.String{Value: "bar"})))
|
|
||||||
|
|
||||||
testBytecodeSerialization(t, bytecodeFileSet(
|
|
||||||
concat(
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 0),
|
|
||||||
compiler.MakeInstruction(compiler.OpSetGlobal, 0),
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 6),
|
|
||||||
compiler.MakeInstruction(compiler.OpPop)),
|
|
||||||
objectsArray(
|
|
||||||
&objects.Int{Value: 55},
|
|
||||||
&objects.Int{Value: 66},
|
|
||||||
&objects.Int{Value: 77},
|
|
||||||
&objects.Int{Value: 88},
|
|
||||||
&objects.ImmutableMap{
|
|
||||||
Value: map[string]objects.Object{
|
|
||||||
"array": &objects.ImmutableArray{
|
|
||||||
Value: []objects.Object{
|
|
||||||
&objects.Int{Value: 1},
|
|
||||||
&objects.Int{Value: 2},
|
|
||||||
&objects.Int{Value: 3},
|
|
||||||
objects.TrueValue,
|
|
||||||
objects.FalseValue,
|
|
||||||
objects.UndefinedValue,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"true": objects.TrueValue,
|
|
||||||
"false": objects.FalseValue,
|
|
||||||
"bytes": &objects.Bytes{Value: make([]byte, 16)},
|
|
||||||
"char": &objects.Char{Value: 'Y'},
|
|
||||||
"error": &objects.Error{Value: &objects.String{Value: "some error"}},
|
|
||||||
"float": &objects.Float{Value: -19.84},
|
|
||||||
"immutable_array": &objects.ImmutableArray{
|
|
||||||
Value: []objects.Object{
|
|
||||||
&objects.Int{Value: 1},
|
|
||||||
&objects.Int{Value: 2},
|
|
||||||
&objects.Int{Value: 3},
|
|
||||||
objects.TrueValue,
|
|
||||||
objects.FalseValue,
|
|
||||||
objects.UndefinedValue,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"immutable_map": &objects.ImmutableMap{
|
|
||||||
Value: map[string]objects.Object{
|
|
||||||
"a": &objects.Int{Value: 1},
|
|
||||||
"b": &objects.Int{Value: 2},
|
|
||||||
"c": &objects.Int{Value: 3},
|
|
||||||
"d": objects.TrueValue,
|
|
||||||
"e": objects.FalseValue,
|
|
||||||
"f": objects.UndefinedValue,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"int": &objects.Int{Value: 91},
|
|
||||||
"map": &objects.Map{
|
|
||||||
Value: map[string]objects.Object{
|
|
||||||
"a": &objects.Int{Value: 1},
|
|
||||||
"b": &objects.Int{Value: 2},
|
|
||||||
"c": &objects.Int{Value: 3},
|
|
||||||
"d": objects.TrueValue,
|
|
||||||
"e": objects.FalseValue,
|
|
||||||
"f": objects.UndefinedValue,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"string": &objects.String{Value: "foo bar"},
|
|
||||||
"time": &objects.Time{Value: time.Now()},
|
|
||||||
"undefined": objects.UndefinedValue,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
compiledFunction(1, 0,
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 3),
|
|
||||||
compiler.MakeInstruction(compiler.OpSetLocal, 0),
|
|
||||||
compiler.MakeInstruction(compiler.OpGetGlobal, 0),
|
|
||||||
compiler.MakeInstruction(compiler.OpGetFree, 0),
|
|
||||||
compiler.MakeInstruction(compiler.OpBinaryOp, 11),
|
|
||||||
compiler.MakeInstruction(compiler.OpGetFree, 1),
|
|
||||||
compiler.MakeInstruction(compiler.OpBinaryOp, 11),
|
|
||||||
compiler.MakeInstruction(compiler.OpGetLocal, 0),
|
|
||||||
compiler.MakeInstruction(compiler.OpBinaryOp, 11),
|
|
||||||
compiler.MakeInstruction(compiler.OpReturn, 1)),
|
|
||||||
compiledFunction(1, 0,
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 2),
|
|
||||||
compiler.MakeInstruction(compiler.OpSetLocal, 0),
|
|
||||||
compiler.MakeInstruction(compiler.OpGetFree, 0),
|
|
||||||
compiler.MakeInstruction(compiler.OpGetLocal, 0),
|
|
||||||
compiler.MakeInstruction(compiler.OpClosure, 4, 2),
|
|
||||||
compiler.MakeInstruction(compiler.OpReturn, 1)),
|
|
||||||
compiledFunction(1, 0,
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 1),
|
|
||||||
compiler.MakeInstruction(compiler.OpSetLocal, 0),
|
|
||||||
compiler.MakeInstruction(compiler.OpGetLocal, 0),
|
|
||||||
compiler.MakeInstruction(compiler.OpClosure, 5, 1),
|
|
||||||
compiler.MakeInstruction(compiler.OpReturn, 1))),
|
|
||||||
fileSet(srcfile{name: "file1", size: 100}, srcfile{name: "file2", size: 200})))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBytecode_RemoveDuplicates(t *testing.T) {
|
|
||||||
testBytecodeRemoveDuplicates(t,
|
|
||||||
bytecode(
|
|
||||||
concat(), objectsArray(
|
|
||||||
&objects.Char{Value: 'y'},
|
|
||||||
&objects.Float{Value: 93.11},
|
|
||||||
compiledFunction(1, 0,
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 3),
|
|
||||||
compiler.MakeInstruction(compiler.OpSetLocal, 0),
|
|
||||||
compiler.MakeInstruction(compiler.OpGetGlobal, 0),
|
|
||||||
compiler.MakeInstruction(compiler.OpGetFree, 0)),
|
|
||||||
&objects.Float{Value: 39.2},
|
|
||||||
&objects.Int{Value: 192},
|
|
||||||
&objects.String{Value: "bar"})),
|
|
||||||
bytecode(
|
|
||||||
concat(), objectsArray(
|
|
||||||
&objects.Char{Value: 'y'},
|
|
||||||
&objects.Float{Value: 93.11},
|
|
||||||
compiledFunction(1, 0,
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 3),
|
|
||||||
compiler.MakeInstruction(compiler.OpSetLocal, 0),
|
|
||||||
compiler.MakeInstruction(compiler.OpGetGlobal, 0),
|
|
||||||
compiler.MakeInstruction(compiler.OpGetFree, 0)),
|
|
||||||
&objects.Float{Value: 39.2},
|
|
||||||
&objects.Int{Value: 192},
|
|
||||||
&objects.String{Value: "bar"})))
|
|
||||||
|
|
||||||
testBytecodeRemoveDuplicates(t,
|
|
||||||
bytecode(
|
|
||||||
concat(
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 0),
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 1),
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 2),
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 3),
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 4),
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 5),
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 6),
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 7),
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 8),
|
|
||||||
compiler.MakeInstruction(compiler.OpClosure, 4, 1)),
|
|
||||||
objectsArray(
|
|
||||||
&objects.Int{Value: 1},
|
|
||||||
&objects.Float{Value: 2.0},
|
|
||||||
&objects.Char{Value: '3'},
|
|
||||||
&objects.String{Value: "four"},
|
|
||||||
compiledFunction(1, 0,
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 3),
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 7),
|
|
||||||
compiler.MakeInstruction(compiler.OpSetLocal, 0),
|
|
||||||
compiler.MakeInstruction(compiler.OpGetGlobal, 0),
|
|
||||||
compiler.MakeInstruction(compiler.OpGetFree, 0)),
|
|
||||||
&objects.Int{Value: 1},
|
|
||||||
&objects.Float{Value: 2.0},
|
|
||||||
&objects.Char{Value: '3'},
|
|
||||||
&objects.String{Value: "four"})),
|
|
||||||
bytecode(
|
|
||||||
concat(
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 0),
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 1),
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 2),
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 3),
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 4),
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 0),
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 1),
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 2),
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 3),
|
|
||||||
compiler.MakeInstruction(compiler.OpClosure, 4, 1)),
|
|
||||||
objectsArray(
|
|
||||||
&objects.Int{Value: 1},
|
|
||||||
&objects.Float{Value: 2.0},
|
|
||||||
&objects.Char{Value: '3'},
|
|
||||||
&objects.String{Value: "four"},
|
|
||||||
compiledFunction(1, 0,
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 3),
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 2),
|
|
||||||
compiler.MakeInstruction(compiler.OpSetLocal, 0),
|
|
||||||
compiler.MakeInstruction(compiler.OpGetGlobal, 0),
|
|
||||||
compiler.MakeInstruction(compiler.OpGetFree, 0)))))
|
|
||||||
|
|
||||||
testBytecodeRemoveDuplicates(t,
|
|
||||||
bytecode(
|
|
||||||
concat(
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 0),
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 1),
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 2),
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 3),
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 4)),
|
|
||||||
objectsArray(
|
|
||||||
&objects.Int{Value: 1},
|
|
||||||
&objects.Int{Value: 2},
|
|
||||||
&objects.Int{Value: 3},
|
|
||||||
&objects.Int{Value: 1},
|
|
||||||
&objects.Int{Value: 3})),
|
|
||||||
bytecode(
|
|
||||||
concat(
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 0),
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 1),
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 2),
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 0),
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 2)),
|
|
||||||
objectsArray(
|
|
||||||
&objects.Int{Value: 1},
|
|
||||||
&objects.Int{Value: 2},
|
|
||||||
&objects.Int{Value: 3})))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBytecode_CountObjects(t *testing.T) {
|
|
||||||
b := bytecode(
|
|
||||||
concat(),
|
|
||||||
objectsArray(
|
|
||||||
&objects.Int{Value: 55},
|
|
||||||
&objects.Int{Value: 66},
|
|
||||||
&objects.Int{Value: 77},
|
|
||||||
&objects.Int{Value: 88},
|
|
||||||
compiledFunction(1, 0,
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 3),
|
|
||||||
compiler.MakeInstruction(compiler.OpReturn, 1)),
|
|
||||||
compiledFunction(1, 0,
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 2),
|
|
||||||
compiler.MakeInstruction(compiler.OpReturn, 1)),
|
|
||||||
compiledFunction(1, 0,
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 1),
|
|
||||||
compiler.MakeInstruction(compiler.OpReturn, 1))))
|
|
||||||
assert.Equal(t, 7, b.CountObjects())
|
|
||||||
}
|
|
||||||
|
|
||||||
func fileSet(files ...srcfile) *source.FileSet {
|
|
||||||
fileSet := source.NewFileSet()
|
|
||||||
for _, f := range files {
|
|
||||||
fileSet.AddFile(f.name, -1, f.size)
|
|
||||||
}
|
|
||||||
return fileSet
|
|
||||||
}
|
|
||||||
|
|
||||||
func bytecodeFileSet(instructions []byte, constants []objects.Object, fileSet *source.FileSet) *compiler.Bytecode {
|
|
||||||
return &compiler.Bytecode{
|
|
||||||
FileSet: fileSet,
|
|
||||||
MainFunction: &objects.CompiledFunction{Instructions: instructions},
|
|
||||||
Constants: constants,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testBytecodeRemoveDuplicates(t *testing.T, input, expected *compiler.Bytecode) {
|
|
||||||
input.RemoveDuplicates()
|
|
||||||
|
|
||||||
assert.Equal(t, expected.FileSet, input.FileSet)
|
|
||||||
assert.Equal(t, expected.MainFunction, input.MainFunction)
|
|
||||||
assert.Equal(t, expected.Constants, input.Constants)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testBytecodeSerialization(t *testing.T, b *compiler.Bytecode) {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
err := b.Encode(&buf)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
r := &compiler.Bytecode{}
|
|
||||||
err = r.Decode(bytes.NewReader(buf.Bytes()), nil)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
assert.Equal(t, b.FileSet, r.FileSet)
|
|
||||||
assert.Equal(t, b.MainFunction, r.MainFunction)
|
|
||||||
assert.Equal(t, b.Constants, r.Constants)
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
package compiler
|
|
||||||
|
|
||||||
import "github.com/d5/tengo/compiler/source"
|
|
||||||
|
|
||||||
// CompilationScope represents a compiled instructions
|
|
||||||
// and the last two instructions that were emitted.
|
|
||||||
type CompilationScope struct {
|
|
||||||
instructions []byte
|
|
||||||
symbolInit map[string]bool
|
|
||||||
sourceMap map[int]source.Pos
|
|
||||||
}
|
|
|
@ -1,846 +0,0 @@
|
||||||
package compiler
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"path/filepath"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/d5/tengo"
|
|
||||||
"github.com/d5/tengo/compiler/ast"
|
|
||||||
"github.com/d5/tengo/compiler/source"
|
|
||||||
"github.com/d5/tengo/compiler/token"
|
|
||||||
"github.com/d5/tengo/objects"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Compiler compiles the AST into a bytecode.
|
|
||||||
type Compiler struct {
|
|
||||||
file *source.File
|
|
||||||
parent *Compiler
|
|
||||||
modulePath string
|
|
||||||
constants []objects.Object
|
|
||||||
symbolTable *SymbolTable
|
|
||||||
scopes []CompilationScope
|
|
||||||
scopeIndex int
|
|
||||||
modules *objects.ModuleMap
|
|
||||||
compiledModules map[string]*objects.CompiledFunction
|
|
||||||
allowFileImport bool
|
|
||||||
loops []*Loop
|
|
||||||
loopIndex int
|
|
||||||
trace io.Writer
|
|
||||||
indent int
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewCompiler creates a Compiler.
|
|
||||||
func NewCompiler(file *source.File, symbolTable *SymbolTable, constants []objects.Object, modules *objects.ModuleMap, trace io.Writer) *Compiler {
|
|
||||||
mainScope := CompilationScope{
|
|
||||||
symbolInit: make(map[string]bool),
|
|
||||||
sourceMap: make(map[int]source.Pos),
|
|
||||||
}
|
|
||||||
|
|
||||||
// symbol table
|
|
||||||
if symbolTable == nil {
|
|
||||||
symbolTable = NewSymbolTable()
|
|
||||||
}
|
|
||||||
|
|
||||||
// add builtin functions to the symbol table
|
|
||||||
for idx, fn := range objects.Builtins {
|
|
||||||
symbolTable.DefineBuiltin(idx, fn.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// builtin modules
|
|
||||||
if modules == nil {
|
|
||||||
modules = objects.NewModuleMap()
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Compiler{
|
|
||||||
file: file,
|
|
||||||
symbolTable: symbolTable,
|
|
||||||
constants: constants,
|
|
||||||
scopes: []CompilationScope{mainScope},
|
|
||||||
scopeIndex: 0,
|
|
||||||
loopIndex: -1,
|
|
||||||
trace: trace,
|
|
||||||
modules: modules,
|
|
||||||
compiledModules: make(map[string]*objects.CompiledFunction),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compile compiles the AST node.
|
|
||||||
func (c *Compiler) Compile(node ast.Node) error {
|
|
||||||
if c.trace != nil {
|
|
||||||
if node != nil {
|
|
||||||
defer un(trace(c, fmt.Sprintf("%s (%s)", node.String(), reflect.TypeOf(node).Elem().Name())))
|
|
||||||
} else {
|
|
||||||
defer un(trace(c, "<nil>"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch node := node.(type) {
|
|
||||||
case *ast.File:
|
|
||||||
for _, stmt := range node.Stmts {
|
|
||||||
if err := c.Compile(stmt); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case *ast.ExprStmt:
|
|
||||||
if err := c.Compile(node.Expr); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.emit(node, OpPop)
|
|
||||||
|
|
||||||
case *ast.IncDecStmt:
|
|
||||||
op := token.AddAssign
|
|
||||||
if node.Token == token.Dec {
|
|
||||||
op = token.SubAssign
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.compileAssign(node, []ast.Expr{node.Expr}, []ast.Expr{&ast.IntLit{Value: 1}}, op)
|
|
||||||
|
|
||||||
case *ast.ParenExpr:
|
|
||||||
if err := c.Compile(node.Expr); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
case *ast.BinaryExpr:
|
|
||||||
if node.Token == token.LAnd || node.Token == token.LOr {
|
|
||||||
return c.compileLogical(node)
|
|
||||||
}
|
|
||||||
|
|
||||||
if node.Token == token.Less {
|
|
||||||
if err := c.Compile(node.RHS); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := c.Compile(node.LHS); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.emit(node, OpBinaryOp, int(token.Greater))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
} else if node.Token == token.LessEq {
|
|
||||||
if err := c.Compile(node.RHS); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := c.Compile(node.LHS); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.emit(node, OpBinaryOp, int(token.GreaterEq))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := c.Compile(node.LHS); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := c.Compile(node.RHS); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch node.Token {
|
|
||||||
case token.Add:
|
|
||||||
c.emit(node, OpBinaryOp, int(token.Add))
|
|
||||||
case token.Sub:
|
|
||||||
c.emit(node, OpBinaryOp, int(token.Sub))
|
|
||||||
case token.Mul:
|
|
||||||
c.emit(node, OpBinaryOp, int(token.Mul))
|
|
||||||
case token.Quo:
|
|
||||||
c.emit(node, OpBinaryOp, int(token.Quo))
|
|
||||||
case token.Rem:
|
|
||||||
c.emit(node, OpBinaryOp, int(token.Rem))
|
|
||||||
case token.Greater:
|
|
||||||
c.emit(node, OpBinaryOp, int(token.Greater))
|
|
||||||
case token.GreaterEq:
|
|
||||||
c.emit(node, OpBinaryOp, int(token.GreaterEq))
|
|
||||||
case token.Equal:
|
|
||||||
c.emit(node, OpEqual)
|
|
||||||
case token.NotEqual:
|
|
||||||
c.emit(node, OpNotEqual)
|
|
||||||
case token.And:
|
|
||||||
c.emit(node, OpBinaryOp, int(token.And))
|
|
||||||
case token.Or:
|
|
||||||
c.emit(node, OpBinaryOp, int(token.Or))
|
|
||||||
case token.Xor:
|
|
||||||
c.emit(node, OpBinaryOp, int(token.Xor))
|
|
||||||
case token.AndNot:
|
|
||||||
c.emit(node, OpBinaryOp, int(token.AndNot))
|
|
||||||
case token.Shl:
|
|
||||||
c.emit(node, OpBinaryOp, int(token.Shl))
|
|
||||||
case token.Shr:
|
|
||||||
c.emit(node, OpBinaryOp, int(token.Shr))
|
|
||||||
default:
|
|
||||||
return c.errorf(node, "invalid binary operator: %s", node.Token.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
case *ast.IntLit:
|
|
||||||
c.emit(node, OpConstant, c.addConstant(&objects.Int{Value: node.Value}))
|
|
||||||
|
|
||||||
case *ast.FloatLit:
|
|
||||||
c.emit(node, OpConstant, c.addConstant(&objects.Float{Value: node.Value}))
|
|
||||||
|
|
||||||
case *ast.BoolLit:
|
|
||||||
if node.Value {
|
|
||||||
c.emit(node, OpTrue)
|
|
||||||
} else {
|
|
||||||
c.emit(node, OpFalse)
|
|
||||||
}
|
|
||||||
|
|
||||||
case *ast.StringLit:
|
|
||||||
if len(node.Value) > tengo.MaxStringLen {
|
|
||||||
return c.error(node, objects.ErrStringLimit)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.emit(node, OpConstant, c.addConstant(&objects.String{Value: node.Value}))
|
|
||||||
|
|
||||||
case *ast.CharLit:
|
|
||||||
c.emit(node, OpConstant, c.addConstant(&objects.Char{Value: node.Value}))
|
|
||||||
|
|
||||||
case *ast.UndefinedLit:
|
|
||||||
c.emit(node, OpNull)
|
|
||||||
|
|
||||||
case *ast.UnaryExpr:
|
|
||||||
if err := c.Compile(node.Expr); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch node.Token {
|
|
||||||
case token.Not:
|
|
||||||
c.emit(node, OpLNot)
|
|
||||||
case token.Sub:
|
|
||||||
c.emit(node, OpMinus)
|
|
||||||
case token.Xor:
|
|
||||||
c.emit(node, OpBComplement)
|
|
||||||
case token.Add:
|
|
||||||
// do nothing?
|
|
||||||
default:
|
|
||||||
return c.errorf(node, "invalid unary operator: %s", node.Token.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
case *ast.IfStmt:
|
|
||||||
// open new symbol table for the statement
|
|
||||||
c.symbolTable = c.symbolTable.Fork(true)
|
|
||||||
defer func() {
|
|
||||||
c.symbolTable = c.symbolTable.Parent(false)
|
|
||||||
}()
|
|
||||||
|
|
||||||
if node.Init != nil {
|
|
||||||
if err := c.Compile(node.Init); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := c.Compile(node.Cond); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// first jump placeholder
|
|
||||||
jumpPos1 := c.emit(node, OpJumpFalsy, 0)
|
|
||||||
|
|
||||||
if err := c.Compile(node.Body); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if node.Else != nil {
|
|
||||||
// second jump placeholder
|
|
||||||
jumpPos2 := c.emit(node, OpJump, 0)
|
|
||||||
|
|
||||||
// update first jump offset
|
|
||||||
curPos := len(c.currentInstructions())
|
|
||||||
c.changeOperand(jumpPos1, curPos)
|
|
||||||
|
|
||||||
if err := c.Compile(node.Else); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// update second jump offset
|
|
||||||
curPos = len(c.currentInstructions())
|
|
||||||
c.changeOperand(jumpPos2, curPos)
|
|
||||||
} else {
|
|
||||||
// update first jump offset
|
|
||||||
curPos := len(c.currentInstructions())
|
|
||||||
c.changeOperand(jumpPos1, curPos)
|
|
||||||
}
|
|
||||||
|
|
||||||
case *ast.ForStmt:
|
|
||||||
return c.compileForStmt(node)
|
|
||||||
|
|
||||||
case *ast.ForInStmt:
|
|
||||||
return c.compileForInStmt(node)
|
|
||||||
|
|
||||||
case *ast.BranchStmt:
|
|
||||||
if node.Token == token.Break {
|
|
||||||
curLoop := c.currentLoop()
|
|
||||||
if curLoop == nil {
|
|
||||||
return c.errorf(node, "break not allowed outside loop")
|
|
||||||
}
|
|
||||||
pos := c.emit(node, OpJump, 0)
|
|
||||||
curLoop.Breaks = append(curLoop.Breaks, pos)
|
|
||||||
} else if node.Token == token.Continue {
|
|
||||||
curLoop := c.currentLoop()
|
|
||||||
if curLoop == nil {
|
|
||||||
return c.errorf(node, "continue not allowed outside loop")
|
|
||||||
}
|
|
||||||
pos := c.emit(node, OpJump, 0)
|
|
||||||
curLoop.Continues = append(curLoop.Continues, pos)
|
|
||||||
} else {
|
|
||||||
panic(fmt.Errorf("invalid branch statement: %s", node.Token.String()))
|
|
||||||
}
|
|
||||||
|
|
||||||
case *ast.BlockStmt:
|
|
||||||
if len(node.Stmts) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
c.symbolTable = c.symbolTable.Fork(true)
|
|
||||||
defer func() {
|
|
||||||
c.symbolTable = c.symbolTable.Parent(false)
|
|
||||||
}()
|
|
||||||
|
|
||||||
for _, stmt := range node.Stmts {
|
|
||||||
if err := c.Compile(stmt); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case *ast.AssignStmt:
|
|
||||||
if err := c.compileAssign(node, node.LHS, node.RHS, node.Token); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
case *ast.Ident:
|
|
||||||
symbol, _, ok := c.symbolTable.Resolve(node.Name)
|
|
||||||
if !ok {
|
|
||||||
return c.errorf(node, "unresolved reference '%s'", node.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch symbol.Scope {
|
|
||||||
case ScopeGlobal:
|
|
||||||
c.emit(node, OpGetGlobal, symbol.Index)
|
|
||||||
case ScopeLocal:
|
|
||||||
c.emit(node, OpGetLocal, symbol.Index)
|
|
||||||
case ScopeBuiltin:
|
|
||||||
c.emit(node, OpGetBuiltin, symbol.Index)
|
|
||||||
case ScopeFree:
|
|
||||||
c.emit(node, OpGetFree, symbol.Index)
|
|
||||||
}
|
|
||||||
|
|
||||||
case *ast.ArrayLit:
|
|
||||||
for _, elem := range node.Elements {
|
|
||||||
if err := c.Compile(elem); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.emit(node, OpArray, len(node.Elements))
|
|
||||||
|
|
||||||
case *ast.MapLit:
|
|
||||||
for _, elt := range node.Elements {
|
|
||||||
// key
|
|
||||||
if len(elt.Key) > tengo.MaxStringLen {
|
|
||||||
return c.error(node, objects.ErrStringLimit)
|
|
||||||
}
|
|
||||||
c.emit(node, OpConstant, c.addConstant(&objects.String{Value: elt.Key}))
|
|
||||||
|
|
||||||
// value
|
|
||||||
if err := c.Compile(elt.Value); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.emit(node, OpMap, len(node.Elements)*2)
|
|
||||||
|
|
||||||
case *ast.SelectorExpr: // selector on RHS side
|
|
||||||
if err := c.Compile(node.Expr); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := c.Compile(node.Sel); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.emit(node, OpIndex)
|
|
||||||
|
|
||||||
case *ast.IndexExpr:
|
|
||||||
if err := c.Compile(node.Expr); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := c.Compile(node.Index); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.emit(node, OpIndex)
|
|
||||||
|
|
||||||
case *ast.SliceExpr:
|
|
||||||
if err := c.Compile(node.Expr); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if node.Low != nil {
|
|
||||||
if err := c.Compile(node.Low); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
c.emit(node, OpNull)
|
|
||||||
}
|
|
||||||
|
|
||||||
if node.High != nil {
|
|
||||||
if err := c.Compile(node.High); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
c.emit(node, OpNull)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.emit(node, OpSliceIndex)
|
|
||||||
|
|
||||||
case *ast.FuncLit:
|
|
||||||
c.enterScope()
|
|
||||||
|
|
||||||
for _, p := range node.Type.Params.List {
|
|
||||||
s := c.symbolTable.Define(p.Name)
|
|
||||||
|
|
||||||
// function arguments is not assigned directly.
|
|
||||||
s.LocalAssigned = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := c.Compile(node.Body); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// code optimization
|
|
||||||
c.optimizeFunc(node)
|
|
||||||
|
|
||||||
freeSymbols := c.symbolTable.FreeSymbols()
|
|
||||||
numLocals := c.symbolTable.MaxSymbols()
|
|
||||||
instructions, sourceMap := c.leaveScope()
|
|
||||||
|
|
||||||
for _, s := range freeSymbols {
|
|
||||||
switch s.Scope {
|
|
||||||
case ScopeLocal:
|
|
||||||
if !s.LocalAssigned {
|
|
||||||
// Here, the closure is capturing a local variable that's not yet assigned its value.
|
|
||||||
// One example is a local recursive function:
|
|
||||||
//
|
|
||||||
// func() {
|
|
||||||
// foo := func(x) {
|
|
||||||
// // ..
|
|
||||||
// return foo(x-1)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// which translate into
|
|
||||||
//
|
|
||||||
// 0000 GETL 0
|
|
||||||
// 0002 CLOSURE ? 1
|
|
||||||
// 0006 DEFL 0
|
|
||||||
//
|
|
||||||
// . So the local variable (0) is being captured before it's assigned the value.
|
|
||||||
//
|
|
||||||
// Solution is to transform the code into something like this:
|
|
||||||
//
|
|
||||||
// func() {
|
|
||||||
// foo := undefined
|
|
||||||
// foo = func(x) {
|
|
||||||
// // ..
|
|
||||||
// return foo(x-1)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// that is equivalent to
|
|
||||||
//
|
|
||||||
// 0000 NULL
|
|
||||||
// 0001 DEFL 0
|
|
||||||
// 0003 GETL 0
|
|
||||||
// 0005 CLOSURE ? 1
|
|
||||||
// 0009 SETL 0
|
|
||||||
//
|
|
||||||
|
|
||||||
c.emit(node, OpNull)
|
|
||||||
c.emit(node, OpDefineLocal, s.Index)
|
|
||||||
|
|
||||||
s.LocalAssigned = true
|
|
||||||
}
|
|
||||||
|
|
||||||
c.emit(node, OpGetLocalPtr, s.Index)
|
|
||||||
case ScopeFree:
|
|
||||||
c.emit(node, OpGetFreePtr, s.Index)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
compiledFunction := &objects.CompiledFunction{
|
|
||||||
Instructions: instructions,
|
|
||||||
NumLocals: numLocals,
|
|
||||||
NumParameters: len(node.Type.Params.List),
|
|
||||||
VarArgs: node.Type.Params.VarArgs,
|
|
||||||
SourceMap: sourceMap,
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(freeSymbols) > 0 {
|
|
||||||
c.emit(node, OpClosure, c.addConstant(compiledFunction), len(freeSymbols))
|
|
||||||
} else {
|
|
||||||
c.emit(node, OpConstant, c.addConstant(compiledFunction))
|
|
||||||
}
|
|
||||||
|
|
||||||
case *ast.ReturnStmt:
|
|
||||||
if c.symbolTable.Parent(true) == nil {
|
|
||||||
// outside the function
|
|
||||||
return c.errorf(node, "return not allowed outside function")
|
|
||||||
}
|
|
||||||
|
|
||||||
if node.Result == nil {
|
|
||||||
c.emit(node, OpReturn, 0)
|
|
||||||
} else {
|
|
||||||
if err := c.Compile(node.Result); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.emit(node, OpReturn, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
case *ast.CallExpr:
|
|
||||||
if err := c.Compile(node.Func); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, arg := range node.Args {
|
|
||||||
if err := c.Compile(arg); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.emit(node, OpCall, len(node.Args))
|
|
||||||
|
|
||||||
case *ast.ImportExpr:
|
|
||||||
if node.ModuleName == "" {
|
|
||||||
return c.errorf(node, "empty module name")
|
|
||||||
}
|
|
||||||
|
|
||||||
if mod := c.modules.Get(node.ModuleName); mod != nil {
|
|
||||||
v, err := mod.Import(node.ModuleName)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch v := v.(type) {
|
|
||||||
case []byte: // module written in Tengo
|
|
||||||
compiled, err := c.compileModule(node, node.ModuleName, node.ModuleName, v)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.emit(node, OpConstant, c.addConstant(compiled))
|
|
||||||
c.emit(node, OpCall, 0)
|
|
||||||
case objects.Object: // builtin module
|
|
||||||
c.emit(node, OpConstant, c.addConstant(v))
|
|
||||||
default:
|
|
||||||
panic(fmt.Errorf("invalid import value type: %T", v))
|
|
||||||
}
|
|
||||||
} else if c.allowFileImport {
|
|
||||||
moduleName := node.ModuleName
|
|
||||||
if !strings.HasSuffix(moduleName, ".tengo") {
|
|
||||||
moduleName += ".tengo"
|
|
||||||
}
|
|
||||||
|
|
||||||
modulePath, err := filepath.Abs(moduleName)
|
|
||||||
if err != nil {
|
|
||||||
return c.errorf(node, "module file path error: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := c.checkCyclicImports(node, modulePath); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
moduleSrc, err := ioutil.ReadFile(moduleName)
|
|
||||||
if err != nil {
|
|
||||||
return c.errorf(node, "module file read error: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
compiled, err := c.compileModule(node, moduleName, modulePath, moduleSrc)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.emit(node, OpConstant, c.addConstant(compiled))
|
|
||||||
c.emit(node, OpCall, 0)
|
|
||||||
} else {
|
|
||||||
return c.errorf(node, "module '%s' not found", node.ModuleName)
|
|
||||||
}
|
|
||||||
|
|
||||||
case *ast.ExportStmt:
|
|
||||||
// export statement must be in top-level scope
|
|
||||||
if c.scopeIndex != 0 {
|
|
||||||
return c.errorf(node, "export not allowed inside function")
|
|
||||||
}
|
|
||||||
|
|
||||||
// export statement is simply ignore when compiling non-module code
|
|
||||||
if c.parent == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := c.Compile(node.Result); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.emit(node, OpImmutable)
|
|
||||||
c.emit(node, OpReturn, 1)
|
|
||||||
|
|
||||||
case *ast.ErrorExpr:
|
|
||||||
if err := c.Compile(node.Expr); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.emit(node, OpError)
|
|
||||||
|
|
||||||
case *ast.ImmutableExpr:
|
|
||||||
if err := c.Compile(node.Expr); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.emit(node, OpImmutable)
|
|
||||||
|
|
||||||
case *ast.CondExpr:
|
|
||||||
if err := c.Compile(node.Cond); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// first jump placeholder
|
|
||||||
jumpPos1 := c.emit(node, OpJumpFalsy, 0)
|
|
||||||
|
|
||||||
if err := c.Compile(node.True); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// second jump placeholder
|
|
||||||
jumpPos2 := c.emit(node, OpJump, 0)
|
|
||||||
|
|
||||||
// update first jump offset
|
|
||||||
curPos := len(c.currentInstructions())
|
|
||||||
c.changeOperand(jumpPos1, curPos)
|
|
||||||
|
|
||||||
if err := c.Compile(node.False); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// update second jump offset
|
|
||||||
curPos = len(c.currentInstructions())
|
|
||||||
c.changeOperand(jumpPos2, curPos)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bytecode returns a compiled bytecode.
|
|
||||||
func (c *Compiler) Bytecode() *Bytecode {
|
|
||||||
return &Bytecode{
|
|
||||||
FileSet: c.file.Set(),
|
|
||||||
MainFunction: &objects.CompiledFunction{
|
|
||||||
Instructions: c.currentInstructions(),
|
|
||||||
SourceMap: c.currentSourceMap(),
|
|
||||||
},
|
|
||||||
Constants: c.constants,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// EnableFileImport enables or disables module loading from local files.
|
|
||||||
// Local file modules are disabled by default.
|
|
||||||
func (c *Compiler) EnableFileImport(enable bool) {
|
|
||||||
c.allowFileImport = enable
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Compiler) fork(file *source.File, modulePath string, symbolTable *SymbolTable) *Compiler {
|
|
||||||
child := NewCompiler(file, symbolTable, nil, c.modules, c.trace)
|
|
||||||
child.modulePath = modulePath // module file path
|
|
||||||
child.parent = c // parent to set to current compiler
|
|
||||||
|
|
||||||
return child
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Compiler) error(node ast.Node, err error) error {
|
|
||||||
return &Error{
|
|
||||||
fileSet: c.file.Set(),
|
|
||||||
node: node,
|
|
||||||
error: err,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Compiler) errorf(node ast.Node, format string, args ...interface{}) error {
|
|
||||||
return &Error{
|
|
||||||
fileSet: c.file.Set(),
|
|
||||||
node: node,
|
|
||||||
error: fmt.Errorf(format, args...),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Compiler) addConstant(o objects.Object) int {
|
|
||||||
if c.parent != nil {
|
|
||||||
// module compilers will use their parent's constants array
|
|
||||||
return c.parent.addConstant(o)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.constants = append(c.constants, o)
|
|
||||||
|
|
||||||
if c.trace != nil {
|
|
||||||
c.printTrace(fmt.Sprintf("CONST %04d %s", len(c.constants)-1, o))
|
|
||||||
}
|
|
||||||
|
|
||||||
return len(c.constants) - 1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Compiler) addInstruction(b []byte) int {
|
|
||||||
posNewIns := len(c.currentInstructions())
|
|
||||||
|
|
||||||
c.scopes[c.scopeIndex].instructions = append(c.currentInstructions(), b...)
|
|
||||||
|
|
||||||
return posNewIns
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Compiler) replaceInstruction(pos int, inst []byte) {
|
|
||||||
copy(c.currentInstructions()[pos:], inst)
|
|
||||||
|
|
||||||
if c.trace != nil {
|
|
||||||
c.printTrace(fmt.Sprintf("REPLC %s",
|
|
||||||
FormatInstructions(c.scopes[c.scopeIndex].instructions[pos:], pos)[0]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Compiler) changeOperand(opPos int, operand ...int) {
|
|
||||||
op := Opcode(c.currentInstructions()[opPos])
|
|
||||||
inst := MakeInstruction(op, operand...)
|
|
||||||
|
|
||||||
c.replaceInstruction(opPos, inst)
|
|
||||||
}
|
|
||||||
|
|
||||||
// optimizeFunc performs some code-level optimization for the current function instructions
|
|
||||||
// it removes unreachable (dead code) instructions and adds "returns" instruction if needed.
|
|
||||||
func (c *Compiler) optimizeFunc(node ast.Node) {
|
|
||||||
// any instructions between RETURN and the function end
|
|
||||||
// or instructions between RETURN and jump target position
|
|
||||||
// are considered as unreachable.
|
|
||||||
|
|
||||||
// pass 1. identify all jump destinations
|
|
||||||
dsts := make(map[int]bool)
|
|
||||||
iterateInstructions(c.scopes[c.scopeIndex].instructions, func(pos int, opcode Opcode, operands []int) bool {
|
|
||||||
switch opcode {
|
|
||||||
case OpJump, OpJumpFalsy, OpAndJump, OpOrJump:
|
|
||||||
dsts[operands[0]] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
|
|
||||||
var newInsts []byte
|
|
||||||
|
|
||||||
// pass 2. eliminate dead code
|
|
||||||
posMap := make(map[int]int) // old position to new position
|
|
||||||
var dstIdx int
|
|
||||||
var deadCode bool
|
|
||||||
iterateInstructions(c.scopes[c.scopeIndex].instructions, func(pos int, opcode Opcode, operands []int) bool {
|
|
||||||
switch {
|
|
||||||
case opcode == OpReturn:
|
|
||||||
if deadCode {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
deadCode = true
|
|
||||||
case dsts[pos]:
|
|
||||||
dstIdx++
|
|
||||||
deadCode = false
|
|
||||||
case deadCode:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
posMap[pos] = len(newInsts)
|
|
||||||
newInsts = append(newInsts, MakeInstruction(opcode, operands...)...)
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
|
|
||||||
// pass 3. update jump positions
|
|
||||||
var lastOp Opcode
|
|
||||||
var appendReturn bool
|
|
||||||
endPos := len(c.scopes[c.scopeIndex].instructions)
|
|
||||||
iterateInstructions(newInsts, func(pos int, opcode Opcode, operands []int) bool {
|
|
||||||
switch opcode {
|
|
||||||
case OpJump, OpJumpFalsy, OpAndJump, OpOrJump:
|
|
||||||
newDst, ok := posMap[operands[0]]
|
|
||||||
if ok {
|
|
||||||
copy(newInsts[pos:], MakeInstruction(opcode, newDst))
|
|
||||||
} else if endPos == operands[0] {
|
|
||||||
// there's a jump instruction that jumps to the end of function
|
|
||||||
// compiler should append "return".
|
|
||||||
appendReturn = true
|
|
||||||
} else {
|
|
||||||
panic(fmt.Errorf("invalid jump position: %d", newDst))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lastOp = opcode
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
if lastOp != OpReturn {
|
|
||||||
appendReturn = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// pass 4. update source map
|
|
||||||
newSourceMap := make(map[int]source.Pos)
|
|
||||||
for pos, srcPos := range c.scopes[c.scopeIndex].sourceMap {
|
|
||||||
newPos, ok := posMap[pos]
|
|
||||||
if ok {
|
|
||||||
newSourceMap[newPos] = srcPos
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.scopes[c.scopeIndex].instructions = newInsts
|
|
||||||
c.scopes[c.scopeIndex].sourceMap = newSourceMap
|
|
||||||
|
|
||||||
// append "return"
|
|
||||||
if appendReturn {
|
|
||||||
c.emit(node, OpReturn, 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Compiler) emit(node ast.Node, opcode Opcode, operands ...int) int {
|
|
||||||
filePos := source.NoPos
|
|
||||||
if node != nil {
|
|
||||||
filePos = node.Pos()
|
|
||||||
}
|
|
||||||
|
|
||||||
inst := MakeInstruction(opcode, operands...)
|
|
||||||
pos := c.addInstruction(inst)
|
|
||||||
c.scopes[c.scopeIndex].sourceMap[pos] = filePos
|
|
||||||
|
|
||||||
if c.trace != nil {
|
|
||||||
c.printTrace(fmt.Sprintf("EMIT %s",
|
|
||||||
FormatInstructions(c.scopes[c.scopeIndex].instructions[pos:], pos)[0]))
|
|
||||||
}
|
|
||||||
|
|
||||||
return pos
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Compiler) printTrace(a ...interface{}) {
|
|
||||||
const (
|
|
||||||
dots = ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . "
|
|
||||||
n = len(dots)
|
|
||||||
)
|
|
||||||
|
|
||||||
i := 2 * c.indent
|
|
||||||
for i > n {
|
|
||||||
_, _ = fmt.Fprint(c.trace, dots)
|
|
||||||
i -= n
|
|
||||||
}
|
|
||||||
_, _ = fmt.Fprint(c.trace, dots[0:i])
|
|
||||||
_, _ = fmt.Fprintln(c.trace, a...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func trace(c *Compiler, msg string) *Compiler {
|
|
||||||
c.printTrace(msg, "{")
|
|
||||||
c.indent++
|
|
||||||
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
func un(c *Compiler) {
|
|
||||||
c.indent--
|
|
||||||
c.printTrace("}")
|
|
||||||
}
|
|
|
@ -1,133 +0,0 @@
|
||||||
package compiler
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/d5/tengo/compiler/ast"
|
|
||||||
"github.com/d5/tengo/compiler/token"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *Compiler) compileAssign(node ast.Node, lhs, rhs []ast.Expr, op token.Token) error {
|
|
||||||
numLHS, numRHS := len(lhs), len(rhs)
|
|
||||||
if numLHS > 1 || numRHS > 1 {
|
|
||||||
return c.errorf(node, "tuple assignment not allowed")
|
|
||||||
}
|
|
||||||
|
|
||||||
// resolve and compile left-hand side
|
|
||||||
ident, selectors := resolveAssignLHS(lhs[0])
|
|
||||||
numSel := len(selectors)
|
|
||||||
|
|
||||||
if op == token.Define && numSel > 0 {
|
|
||||||
// using selector on new variable does not make sense
|
|
||||||
return c.errorf(node, "operator ':=' not allowed with selector")
|
|
||||||
}
|
|
||||||
|
|
||||||
symbol, depth, exists := c.symbolTable.Resolve(ident)
|
|
||||||
if op == token.Define {
|
|
||||||
if depth == 0 && exists {
|
|
||||||
return c.errorf(node, "'%s' redeclared in this block", ident)
|
|
||||||
}
|
|
||||||
|
|
||||||
symbol = c.symbolTable.Define(ident)
|
|
||||||
} else {
|
|
||||||
if !exists {
|
|
||||||
return c.errorf(node, "unresolved reference '%s'", ident)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// +=, -=, *=, /=
|
|
||||||
if op != token.Assign && op != token.Define {
|
|
||||||
if err := c.Compile(lhs[0]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// compile RHSs
|
|
||||||
for _, expr := range rhs {
|
|
||||||
if err := c.Compile(expr); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch op {
|
|
||||||
case token.AddAssign:
|
|
||||||
c.emit(node, OpBinaryOp, int(token.Add))
|
|
||||||
case token.SubAssign:
|
|
||||||
c.emit(node, OpBinaryOp, int(token.Sub))
|
|
||||||
case token.MulAssign:
|
|
||||||
c.emit(node, OpBinaryOp, int(token.Mul))
|
|
||||||
case token.QuoAssign:
|
|
||||||
c.emit(node, OpBinaryOp, int(token.Quo))
|
|
||||||
case token.RemAssign:
|
|
||||||
c.emit(node, OpBinaryOp, int(token.Rem))
|
|
||||||
case token.AndAssign:
|
|
||||||
c.emit(node, OpBinaryOp, int(token.And))
|
|
||||||
case token.OrAssign:
|
|
||||||
c.emit(node, OpBinaryOp, int(token.Or))
|
|
||||||
case token.AndNotAssign:
|
|
||||||
c.emit(node, OpBinaryOp, int(token.AndNot))
|
|
||||||
case token.XorAssign:
|
|
||||||
c.emit(node, OpBinaryOp, int(token.Xor))
|
|
||||||
case token.ShlAssign:
|
|
||||||
c.emit(node, OpBinaryOp, int(token.Shl))
|
|
||||||
case token.ShrAssign:
|
|
||||||
c.emit(node, OpBinaryOp, int(token.Shr))
|
|
||||||
}
|
|
||||||
|
|
||||||
// compile selector expressions (right to left)
|
|
||||||
for i := numSel - 1; i >= 0; i-- {
|
|
||||||
if err := c.Compile(selectors[i]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch symbol.Scope {
|
|
||||||
case ScopeGlobal:
|
|
||||||
if numSel > 0 {
|
|
||||||
c.emit(node, OpSetSelGlobal, symbol.Index, numSel)
|
|
||||||
} else {
|
|
||||||
c.emit(node, OpSetGlobal, symbol.Index)
|
|
||||||
}
|
|
||||||
case ScopeLocal:
|
|
||||||
if numSel > 0 {
|
|
||||||
c.emit(node, OpSetSelLocal, symbol.Index, numSel)
|
|
||||||
} else {
|
|
||||||
if op == token.Define && !symbol.LocalAssigned {
|
|
||||||
c.emit(node, OpDefineLocal, symbol.Index)
|
|
||||||
} else {
|
|
||||||
c.emit(node, OpSetLocal, symbol.Index)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// mark the symbol as local-assigned
|
|
||||||
symbol.LocalAssigned = true
|
|
||||||
case ScopeFree:
|
|
||||||
if numSel > 0 {
|
|
||||||
c.emit(node, OpSetSelFree, symbol.Index, numSel)
|
|
||||||
} else {
|
|
||||||
c.emit(node, OpSetFree, symbol.Index)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
panic(fmt.Errorf("invalid assignment variable scope: %s", symbol.Scope))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func resolveAssignLHS(expr ast.Expr) (name string, selectors []ast.Expr) {
|
|
||||||
switch term := expr.(type) {
|
|
||||||
case *ast.SelectorExpr:
|
|
||||||
name, selectors = resolveAssignLHS(term.Expr)
|
|
||||||
selectors = append(selectors, term.Sel)
|
|
||||||
return
|
|
||||||
|
|
||||||
case *ast.IndexExpr:
|
|
||||||
name, selectors = resolveAssignLHS(term.Expr)
|
|
||||||
selectors = append(selectors, term.Index)
|
|
||||||
|
|
||||||
case *ast.Ident:
|
|
||||||
name = term.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
package compiler_test
|
|
||||||
|
|
||||||
import "testing"
|
|
||||||
|
|
||||||
func TestCompilerErrorReport(t *testing.T) {
|
|
||||||
expectError(t, `import("user1")`, "Compile Error: module 'user1' not found\n\tat test:1:1")
|
|
||||||
|
|
||||||
expectError(t, `a = 1`, "Compile Error: unresolved reference 'a'\n\tat test:1:1")
|
|
||||||
expectError(t, `a, b := 1, 2`, "Compile Error: tuple assignment not allowed\n\tat test:1:1")
|
|
||||||
expectError(t, `a.b := 1`, "not allowed with selector")
|
|
||||||
expectError(t, `a:=1; a:=3`, "Compile Error: 'a' redeclared in this block\n\tat test:1:7")
|
|
||||||
|
|
||||||
expectError(t, `return 5`, "Compile Error: return not allowed outside function\n\tat test:1:1")
|
|
||||||
expectError(t, `func() { break }`, "Compile Error: break not allowed outside loop\n\tat test:1:10")
|
|
||||||
expectError(t, `func() { continue }`, "Compile Error: continue not allowed outside loop\n\tat test:1:10")
|
|
||||||
expectError(t, `func() { export 5 }`, "Compile Error: export not allowed inside function\n\tat test:1:10")
|
|
||||||
}
|
|
|
@ -1,181 +0,0 @@
|
||||||
package compiler
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/d5/tengo/compiler/ast"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *Compiler) compileForStmt(stmt *ast.ForStmt) error {
|
|
||||||
c.symbolTable = c.symbolTable.Fork(true)
|
|
||||||
defer func() {
|
|
||||||
c.symbolTable = c.symbolTable.Parent(false)
|
|
||||||
}()
|
|
||||||
|
|
||||||
// init statement
|
|
||||||
if stmt.Init != nil {
|
|
||||||
if err := c.Compile(stmt.Init); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// pre-condition position
|
|
||||||
preCondPos := len(c.currentInstructions())
|
|
||||||
|
|
||||||
// condition expression
|
|
||||||
postCondPos := -1
|
|
||||||
if stmt.Cond != nil {
|
|
||||||
if err := c.Compile(stmt.Cond); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// condition jump position
|
|
||||||
postCondPos = c.emit(stmt, OpJumpFalsy, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// enter loop
|
|
||||||
loop := c.enterLoop()
|
|
||||||
|
|
||||||
// body statement
|
|
||||||
if err := c.Compile(stmt.Body); err != nil {
|
|
||||||
c.leaveLoop()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.leaveLoop()
|
|
||||||
|
|
||||||
// post-body position
|
|
||||||
postBodyPos := len(c.currentInstructions())
|
|
||||||
|
|
||||||
// post statement
|
|
||||||
if stmt.Post != nil {
|
|
||||||
if err := c.Compile(stmt.Post); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// back to condition
|
|
||||||
c.emit(stmt, OpJump, preCondPos)
|
|
||||||
|
|
||||||
// post-statement position
|
|
||||||
postStmtPos := len(c.currentInstructions())
|
|
||||||
if postCondPos >= 0 {
|
|
||||||
c.changeOperand(postCondPos, postStmtPos)
|
|
||||||
}
|
|
||||||
|
|
||||||
// update all break/continue jump positions
|
|
||||||
for _, pos := range loop.Breaks {
|
|
||||||
c.changeOperand(pos, postStmtPos)
|
|
||||||
}
|
|
||||||
for _, pos := range loop.Continues {
|
|
||||||
c.changeOperand(pos, postBodyPos)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Compiler) compileForInStmt(stmt *ast.ForInStmt) error {
|
|
||||||
c.symbolTable = c.symbolTable.Fork(true)
|
|
||||||
defer func() {
|
|
||||||
c.symbolTable = c.symbolTable.Parent(false)
|
|
||||||
}()
|
|
||||||
|
|
||||||
// for-in statement is compiled like following:
|
|
||||||
//
|
|
||||||
// for :it := iterator(iterable); :it.next(); {
|
|
||||||
// k, v := :it.get() // DEFINE operator
|
|
||||||
//
|
|
||||||
// ... body ...
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// ":it" is a local variable but will be conflict with other user variables
|
|
||||||
// because character ":" is not allowed.
|
|
||||||
|
|
||||||
// init
|
|
||||||
// :it = iterator(iterable)
|
|
||||||
itSymbol := c.symbolTable.Define(":it")
|
|
||||||
if err := c.Compile(stmt.Iterable); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.emit(stmt, OpIteratorInit)
|
|
||||||
if itSymbol.Scope == ScopeGlobal {
|
|
||||||
c.emit(stmt, OpSetGlobal, itSymbol.Index)
|
|
||||||
} else {
|
|
||||||
c.emit(stmt, OpDefineLocal, itSymbol.Index)
|
|
||||||
}
|
|
||||||
|
|
||||||
// pre-condition position
|
|
||||||
preCondPos := len(c.currentInstructions())
|
|
||||||
|
|
||||||
// condition
|
|
||||||
// :it.HasMore()
|
|
||||||
if itSymbol.Scope == ScopeGlobal {
|
|
||||||
c.emit(stmt, OpGetGlobal, itSymbol.Index)
|
|
||||||
} else {
|
|
||||||
c.emit(stmt, OpGetLocal, itSymbol.Index)
|
|
||||||
}
|
|
||||||
c.emit(stmt, OpIteratorNext)
|
|
||||||
|
|
||||||
// condition jump position
|
|
||||||
postCondPos := c.emit(stmt, OpJumpFalsy, 0)
|
|
||||||
|
|
||||||
// enter loop
|
|
||||||
loop := c.enterLoop()
|
|
||||||
|
|
||||||
// assign key variable
|
|
||||||
if stmt.Key.Name != "_" {
|
|
||||||
keySymbol := c.symbolTable.Define(stmt.Key.Name)
|
|
||||||
if itSymbol.Scope == ScopeGlobal {
|
|
||||||
c.emit(stmt, OpGetGlobal, itSymbol.Index)
|
|
||||||
} else {
|
|
||||||
c.emit(stmt, OpGetLocal, itSymbol.Index)
|
|
||||||
}
|
|
||||||
c.emit(stmt, OpIteratorKey)
|
|
||||||
if keySymbol.Scope == ScopeGlobal {
|
|
||||||
c.emit(stmt, OpSetGlobal, keySymbol.Index)
|
|
||||||
} else {
|
|
||||||
c.emit(stmt, OpDefineLocal, keySymbol.Index)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// assign value variable
|
|
||||||
if stmt.Value.Name != "_" {
|
|
||||||
valueSymbol := c.symbolTable.Define(stmt.Value.Name)
|
|
||||||
if itSymbol.Scope == ScopeGlobal {
|
|
||||||
c.emit(stmt, OpGetGlobal, itSymbol.Index)
|
|
||||||
} else {
|
|
||||||
c.emit(stmt, OpGetLocal, itSymbol.Index)
|
|
||||||
}
|
|
||||||
c.emit(stmt, OpIteratorValue)
|
|
||||||
if valueSymbol.Scope == ScopeGlobal {
|
|
||||||
c.emit(stmt, OpSetGlobal, valueSymbol.Index)
|
|
||||||
} else {
|
|
||||||
c.emit(stmt, OpDefineLocal, valueSymbol.Index)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// body statement
|
|
||||||
if err := c.Compile(stmt.Body); err != nil {
|
|
||||||
c.leaveLoop()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.leaveLoop()
|
|
||||||
|
|
||||||
// post-body position
|
|
||||||
postBodyPos := len(c.currentInstructions())
|
|
||||||
|
|
||||||
// back to condition
|
|
||||||
c.emit(stmt, OpJump, preCondPos)
|
|
||||||
|
|
||||||
// post-statement position
|
|
||||||
postStmtPos := len(c.currentInstructions())
|
|
||||||
c.changeOperand(postCondPos, postStmtPos)
|
|
||||||
|
|
||||||
// update all break/continue jump positions
|
|
||||||
for _, pos := range loop.Breaks {
|
|
||||||
c.changeOperand(pos, postStmtPos)
|
|
||||||
}
|
|
||||||
for _, pos := range loop.Continues {
|
|
||||||
c.changeOperand(pos, postBodyPos)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
package compiler
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/d5/tengo/compiler/ast"
|
|
||||||
"github.com/d5/tengo/compiler/token"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *Compiler) compileLogical(node *ast.BinaryExpr) error {
|
|
||||||
// left side term
|
|
||||||
if err := c.Compile(node.LHS); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// jump position
|
|
||||||
var jumpPos int
|
|
||||||
if node.Token == token.LAnd {
|
|
||||||
jumpPos = c.emit(node, OpAndJump, 0)
|
|
||||||
} else {
|
|
||||||
jumpPos = c.emit(node, OpOrJump, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// right side term
|
|
||||||
if err := c.Compile(node.RHS); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.changeOperand(jumpPos, len(c.currentInstructions()))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,31 +0,0 @@
|
||||||
package compiler
|
|
||||||
|
|
||||||
func (c *Compiler) enterLoop() *Loop {
|
|
||||||
loop := &Loop{}
|
|
||||||
|
|
||||||
c.loops = append(c.loops, loop)
|
|
||||||
c.loopIndex++
|
|
||||||
|
|
||||||
if c.trace != nil {
|
|
||||||
c.printTrace("LOOPE", c.loopIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
return loop
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Compiler) leaveLoop() {
|
|
||||||
if c.trace != nil {
|
|
||||||
c.printTrace("LOOPL", c.loopIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.loops = c.loops[:len(c.loops)-1]
|
|
||||||
c.loopIndex--
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Compiler) currentLoop() *Loop {
|
|
||||||
if c.loopIndex >= 0 {
|
|
||||||
return c.loops[c.loopIndex]
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,79 +0,0 @@
|
||||||
package compiler
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/d5/tengo/compiler/ast"
|
|
||||||
"github.com/d5/tengo/compiler/parser"
|
|
||||||
"github.com/d5/tengo/objects"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *Compiler) checkCyclicImports(node ast.Node, modulePath string) error {
|
|
||||||
if c.modulePath == modulePath {
|
|
||||||
return c.errorf(node, "cyclic module import: %s", modulePath)
|
|
||||||
} else if c.parent != nil {
|
|
||||||
return c.parent.checkCyclicImports(node, modulePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Compiler) compileModule(node ast.Node, moduleName, modulePath string, src []byte) (*objects.CompiledFunction, error) {
|
|
||||||
if err := c.checkCyclicImports(node, modulePath); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
compiledModule, exists := c.loadCompiledModule(modulePath)
|
|
||||||
if exists {
|
|
||||||
return compiledModule, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
modFile := c.file.Set().AddFile(moduleName, -1, len(src))
|
|
||||||
p := parser.NewParser(modFile, src, nil)
|
|
||||||
file, err := p.ParseFile()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
symbolTable := NewSymbolTable()
|
|
||||||
|
|
||||||
// inherit builtin functions
|
|
||||||
for _, sym := range c.symbolTable.BuiltinSymbols() {
|
|
||||||
symbolTable.DefineBuiltin(sym.Index, sym.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// no global scope for the module
|
|
||||||
symbolTable = symbolTable.Fork(false)
|
|
||||||
|
|
||||||
// compile module
|
|
||||||
moduleCompiler := c.fork(modFile, modulePath, symbolTable)
|
|
||||||
if err := moduleCompiler.Compile(file); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// code optimization
|
|
||||||
moduleCompiler.optimizeFunc(node)
|
|
||||||
|
|
||||||
compiledFunc := moduleCompiler.Bytecode().MainFunction
|
|
||||||
compiledFunc.NumLocals = symbolTable.MaxSymbols()
|
|
||||||
|
|
||||||
c.storeCompiledModule(modulePath, compiledFunc)
|
|
||||||
|
|
||||||
return compiledFunc, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Compiler) loadCompiledModule(modulePath string) (mod *objects.CompiledFunction, ok bool) {
|
|
||||||
if c.parent != nil {
|
|
||||||
return c.parent.loadCompiledModule(modulePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
mod, ok = c.compiledModules[modulePath]
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Compiler) storeCompiledModule(modulePath string, module *objects.CompiledFunction) {
|
|
||||||
if c.parent != nil {
|
|
||||||
c.parent.storeCompiledModule(modulePath, module)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.compiledModules[modulePath] = module
|
|
||||||
}
|
|
|
@ -1,124 +0,0 @@
|
||||||
package compiler_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/d5/tengo/compiler"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestCompilerDeadCode(t *testing.T) {
|
|
||||||
expect(t, `
|
|
||||||
func() {
|
|
||||||
a := 4
|
|
||||||
return a
|
|
||||||
|
|
||||||
b := 5 // dead code from here
|
|
||||||
c := a
|
|
||||||
return b
|
|
||||||
}`,
|
|
||||||
bytecode(
|
|
||||||
concat(
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 2),
|
|
||||||
compiler.MakeInstruction(compiler.OpPop)),
|
|
||||||
objectsArray(
|
|
||||||
intObject(4),
|
|
||||||
intObject(5),
|
|
||||||
compiledFunction(0, 0,
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 0),
|
|
||||||
compiler.MakeInstruction(compiler.OpDefineLocal, 0),
|
|
||||||
compiler.MakeInstruction(compiler.OpGetLocal, 0),
|
|
||||||
compiler.MakeInstruction(compiler.OpReturn, 1)))))
|
|
||||||
|
|
||||||
expect(t, `
|
|
||||||
func() {
|
|
||||||
if true {
|
|
||||||
return 5
|
|
||||||
a := 4 // dead code from here
|
|
||||||
b := a
|
|
||||||
return b
|
|
||||||
} else {
|
|
||||||
return 4
|
|
||||||
c := 5 // dead code from here
|
|
||||||
d := c
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
}`, bytecode(
|
|
||||||
concat(
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 2),
|
|
||||||
compiler.MakeInstruction(compiler.OpPop)),
|
|
||||||
objectsArray(
|
|
||||||
intObject(5),
|
|
||||||
intObject(4),
|
|
||||||
compiledFunction(0, 0,
|
|
||||||
compiler.MakeInstruction(compiler.OpTrue),
|
|
||||||
compiler.MakeInstruction(compiler.OpJumpFalsy, 9),
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 0),
|
|
||||||
compiler.MakeInstruction(compiler.OpReturn, 1),
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 1),
|
|
||||||
compiler.MakeInstruction(compiler.OpReturn, 1)))))
|
|
||||||
|
|
||||||
expect(t, `
|
|
||||||
func() {
|
|
||||||
a := 1
|
|
||||||
for {
|
|
||||||
if a == 5 {
|
|
||||||
return 10
|
|
||||||
}
|
|
||||||
5 + 5
|
|
||||||
return 20
|
|
||||||
b := a
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
}`, bytecode(
|
|
||||||
concat(
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 4),
|
|
||||||
compiler.MakeInstruction(compiler.OpPop)),
|
|
||||||
objectsArray(
|
|
||||||
intObject(1),
|
|
||||||
intObject(5),
|
|
||||||
intObject(10),
|
|
||||||
intObject(20),
|
|
||||||
compiledFunction(0, 0,
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 0),
|
|
||||||
compiler.MakeInstruction(compiler.OpDefineLocal, 0),
|
|
||||||
compiler.MakeInstruction(compiler.OpGetLocal, 0),
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 1),
|
|
||||||
compiler.MakeInstruction(compiler.OpEqual),
|
|
||||||
compiler.MakeInstruction(compiler.OpJumpFalsy, 19),
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 2),
|
|
||||||
compiler.MakeInstruction(compiler.OpReturn, 1),
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 1),
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 1),
|
|
||||||
compiler.MakeInstruction(compiler.OpBinaryOp, 11),
|
|
||||||
compiler.MakeInstruction(compiler.OpPop),
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 3),
|
|
||||||
compiler.MakeInstruction(compiler.OpReturn, 1)))))
|
|
||||||
|
|
||||||
expect(t, `
|
|
||||||
func() {
|
|
||||||
if true {
|
|
||||||
return 5
|
|
||||||
a := 4 // dead code from here
|
|
||||||
b := a
|
|
||||||
return b
|
|
||||||
} else {
|
|
||||||
return 4
|
|
||||||
c := 5 // dead code from here
|
|
||||||
d := c
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
}`, bytecode(
|
|
||||||
concat(
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 2),
|
|
||||||
compiler.MakeInstruction(compiler.OpPop)),
|
|
||||||
objectsArray(
|
|
||||||
intObject(5),
|
|
||||||
intObject(4),
|
|
||||||
compiledFunction(0, 0,
|
|
||||||
compiler.MakeInstruction(compiler.OpTrue),
|
|
||||||
compiler.MakeInstruction(compiler.OpJumpFalsy, 9),
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 0),
|
|
||||||
compiler.MakeInstruction(compiler.OpReturn, 1),
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 1),
|
|
||||||
compiler.MakeInstruction(compiler.OpReturn, 1)))))
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
package compiler
|
|
||||||
|
|
||||||
import "github.com/d5/tengo/compiler/source"
|
|
||||||
|
|
||||||
func (c *Compiler) currentInstructions() []byte {
|
|
||||||
return c.scopes[c.scopeIndex].instructions
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Compiler) currentSourceMap() map[int]source.Pos {
|
|
||||||
return c.scopes[c.scopeIndex].sourceMap
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Compiler) enterScope() {
|
|
||||||
scope := CompilationScope{
|
|
||||||
symbolInit: make(map[string]bool),
|
|
||||||
sourceMap: make(map[int]source.Pos),
|
|
||||||
}
|
|
||||||
|
|
||||||
c.scopes = append(c.scopes, scope)
|
|
||||||
c.scopeIndex++
|
|
||||||
|
|
||||||
c.symbolTable = c.symbolTable.Fork(false)
|
|
||||||
|
|
||||||
if c.trace != nil {
|
|
||||||
c.printTrace("SCOPE", c.scopeIndex)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Compiler) leaveScope() (instructions []byte, sourceMap map[int]source.Pos) {
|
|
||||||
instructions = c.currentInstructions()
|
|
||||||
sourceMap = c.currentSourceMap()
|
|
||||||
|
|
||||||
c.scopes = c.scopes[:len(c.scopes)-1]
|
|
||||||
c.scopeIndex--
|
|
||||||
|
|
||||||
c.symbolTable = c.symbolTable.Parent(true)
|
|
||||||
|
|
||||||
if c.trace != nil {
|
|
||||||
c.printTrace("SCOPL", c.scopeIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
|
@ -1,69 +0,0 @@
|
||||||
package compiler_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/d5/tengo/compiler"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestCompilerScopes(t *testing.T) {
|
|
||||||
expect(t, `
|
|
||||||
if a := 1; a {
|
|
||||||
a = 2
|
|
||||||
b := a
|
|
||||||
} else {
|
|
||||||
a = 3
|
|
||||||
b := a
|
|
||||||
}`, bytecode(
|
|
||||||
concat(
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 0),
|
|
||||||
compiler.MakeInstruction(compiler.OpSetGlobal, 0),
|
|
||||||
compiler.MakeInstruction(compiler.OpGetGlobal, 0),
|
|
||||||
compiler.MakeInstruction(compiler.OpJumpFalsy, 27),
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 1),
|
|
||||||
compiler.MakeInstruction(compiler.OpSetGlobal, 0),
|
|
||||||
compiler.MakeInstruction(compiler.OpGetGlobal, 0),
|
|
||||||
compiler.MakeInstruction(compiler.OpSetGlobal, 1),
|
|
||||||
compiler.MakeInstruction(compiler.OpJump, 39),
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 2),
|
|
||||||
compiler.MakeInstruction(compiler.OpSetGlobal, 0),
|
|
||||||
compiler.MakeInstruction(compiler.OpGetGlobal, 0),
|
|
||||||
compiler.MakeInstruction(compiler.OpSetGlobal, 1)),
|
|
||||||
objectsArray(
|
|
||||||
intObject(1),
|
|
||||||
intObject(2),
|
|
||||||
intObject(3))))
|
|
||||||
|
|
||||||
expect(t, `
|
|
||||||
func() {
|
|
||||||
if a := 1; a {
|
|
||||||
a = 2
|
|
||||||
b := a
|
|
||||||
} else {
|
|
||||||
a = 3
|
|
||||||
b := a
|
|
||||||
}
|
|
||||||
}`, bytecode(
|
|
||||||
concat(
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 3),
|
|
||||||
compiler.MakeInstruction(compiler.OpPop)),
|
|
||||||
objectsArray(
|
|
||||||
intObject(1),
|
|
||||||
intObject(2),
|
|
||||||
intObject(3),
|
|
||||||
compiledFunction(0, 0,
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 0),
|
|
||||||
compiler.MakeInstruction(compiler.OpDefineLocal, 0),
|
|
||||||
compiler.MakeInstruction(compiler.OpGetLocal, 0),
|
|
||||||
compiler.MakeInstruction(compiler.OpJumpFalsy, 22),
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 1),
|
|
||||||
compiler.MakeInstruction(compiler.OpSetLocal, 0),
|
|
||||||
compiler.MakeInstruction(compiler.OpGetLocal, 0),
|
|
||||||
compiler.MakeInstruction(compiler.OpDefineLocal, 1),
|
|
||||||
compiler.MakeInstruction(compiler.OpJump, 31),
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 2),
|
|
||||||
compiler.MakeInstruction(compiler.OpSetLocal, 0),
|
|
||||||
compiler.MakeInstruction(compiler.OpGetLocal, 0),
|
|
||||||
compiler.MakeInstruction(compiler.OpDefineLocal, 1),
|
|
||||||
compiler.MakeInstruction(compiler.OpReturn, 0)))))
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,8 +0,0 @@
|
||||||
package compiler
|
|
||||||
|
|
||||||
// EmittedInstruction represents an opcode
|
|
||||||
// with its emitted position.
|
|
||||||
type EmittedInstruction struct {
|
|
||||||
Opcode Opcode
|
|
||||||
Position int
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
package compiler
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/d5/tengo/compiler/ast"
|
|
||||||
"github.com/d5/tengo/compiler/source"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Error represents a compiler error.
|
|
||||||
type Error struct {
|
|
||||||
fileSet *source.FileSet
|
|
||||||
node ast.Node
|
|
||||||
error error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Error) Error() string {
|
|
||||||
filePos := e.fileSet.Position(e.node.Pos())
|
|
||||||
return fmt.Sprintf("Compile Error: %s\n\tat %s", e.error.Error(), filePos)
|
|
||||||
}
|
|
|
@ -1,65 +0,0 @@
|
||||||
package compiler_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/d5/tengo/assert"
|
|
||||||
"github.com/d5/tengo/compiler"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestInstructions_String(t *testing.T) {
|
|
||||||
assertInstructionString(t,
|
|
||||||
[][]byte{
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 1),
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 2),
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 65535),
|
|
||||||
},
|
|
||||||
`0000 CONST 1
|
|
||||||
0003 CONST 2
|
|
||||||
0006 CONST 65535`)
|
|
||||||
|
|
||||||
assertInstructionString(t,
|
|
||||||
[][]byte{
|
|
||||||
compiler.MakeInstruction(compiler.OpBinaryOp, 11),
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 2),
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 65535),
|
|
||||||
},
|
|
||||||
`0000 BINARYOP 11
|
|
||||||
0002 CONST 2
|
|
||||||
0005 CONST 65535`)
|
|
||||||
|
|
||||||
assertInstructionString(t,
|
|
||||||
[][]byte{
|
|
||||||
compiler.MakeInstruction(compiler.OpBinaryOp, 11),
|
|
||||||
compiler.MakeInstruction(compiler.OpGetLocal, 1),
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 2),
|
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 65535),
|
|
||||||
},
|
|
||||||
`0000 BINARYOP 11
|
|
||||||
0002 GETL 1
|
|
||||||
0004 CONST 2
|
|
||||||
0007 CONST 65535`)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMakeInstruction(t *testing.T) {
|
|
||||||
makeInstruction(t, []byte{byte(compiler.OpConstant), 0, 0}, compiler.OpConstant, 0)
|
|
||||||
makeInstruction(t, []byte{byte(compiler.OpConstant), 0, 1}, compiler.OpConstant, 1)
|
|
||||||
makeInstruction(t, []byte{byte(compiler.OpConstant), 255, 254}, compiler.OpConstant, 65534)
|
|
||||||
makeInstruction(t, []byte{byte(compiler.OpPop)}, compiler.OpPop)
|
|
||||||
makeInstruction(t, []byte{byte(compiler.OpTrue)}, compiler.OpTrue)
|
|
||||||
makeInstruction(t, []byte{byte(compiler.OpFalse)}, compiler.OpFalse)
|
|
||||||
}
|
|
||||||
|
|
||||||
func assertInstructionString(t *testing.T, instructions [][]byte, expected string) {
|
|
||||||
concatted := make([]byte, 0)
|
|
||||||
for _, e := range instructions {
|
|
||||||
concatted = append(concatted, e...)
|
|
||||||
}
|
|
||||||
assert.Equal(t, expected, strings.Join(compiler.FormatInstructions(concatted, 0), "\n"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeInstruction(t *testing.T, expected []byte, opcode compiler.Opcode, operands ...int) {
|
|
||||||
inst := compiler.MakeInstruction(opcode, operands...)
|
|
||||||
assert.Equal(t, expected, []byte(inst))
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
package compiler
|
|
||||||
|
|
||||||
// Loop represents a loop construct that
|
|
||||||
// the compiler uses to track the current loop.
|
|
||||||
type Loop struct {
|
|
||||||
Continues []int
|
|
||||||
Breaks []int
|
|
||||||
}
|
|
|
@ -1,4 +0,0 @@
|
||||||
package compiler
|
|
||||||
|
|
||||||
// ModuleLoader should take a module name and return the module data.
|
|
||||||
type ModuleLoader func(moduleName string) ([]byte, error)
|
|
|
@ -1,20 +0,0 @@
|
||||||
package compiler_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/d5/tengo/assert"
|
|
||||||
"github.com/d5/tengo/compiler"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestReadOperands(t *testing.T) {
|
|
||||||
assertReadOperand(t, compiler.OpConstant, []int{65535}, 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
func assertReadOperand(t *testing.T, opcode compiler.Opcode, operands []int, expectedBytes int) {
|
|
||||||
inst := compiler.MakeInstruction(opcode, operands...)
|
|
||||||
numOperands := compiler.OpcodeOperands[opcode]
|
|
||||||
operandsRead, read := compiler.ReadOperands(numOperands, inst[1:])
|
|
||||||
assert.Equal(t, expectedBytes, read)
|
|
||||||
assert.Equal(t, operands, operandsRead)
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
package parser
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/d5/tengo/compiler/source"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Error represents a parser error.
|
|
||||||
type Error struct {
|
|
||||||
Pos source.FilePos
|
|
||||||
Msg string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e Error) Error() string {
|
|
||||||
if e.Pos.Filename != "" || e.Pos.IsValid() {
|
|
||||||
return fmt.Sprintf("Parse Error: %s\n\tat %s", e.Msg, e.Pos)
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf("Parse Error: %s", e.Msg)
|
|
||||||
}
|
|
|
@ -1,68 +0,0 @@
|
||||||
package parser
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
"github.com/d5/tengo/compiler/source"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ErrorList is a collection of parser errors.
|
|
||||||
type ErrorList []*Error
|
|
||||||
|
|
||||||
// Add adds a new parser error to the collection.
|
|
||||||
func (p *ErrorList) Add(pos source.FilePos, msg string) {
|
|
||||||
*p = append(*p, &Error{pos, msg})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Len returns the number of elements in the collection.
|
|
||||||
func (p ErrorList) Len() int {
|
|
||||||
return len(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p ErrorList) Swap(i, j int) {
|
|
||||||
p[i], p[j] = p[j], p[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p ErrorList) Less(i, j int) bool {
|
|
||||||
e := &p[i].Pos
|
|
||||||
f := &p[j].Pos
|
|
||||||
|
|
||||||
if e.Filename != f.Filename {
|
|
||||||
return e.Filename < f.Filename
|
|
||||||
}
|
|
||||||
|
|
||||||
if e.Line != f.Line {
|
|
||||||
return e.Line < f.Line
|
|
||||||
}
|
|
||||||
|
|
||||||
if e.Column != f.Column {
|
|
||||||
return e.Column < f.Column
|
|
||||||
}
|
|
||||||
|
|
||||||
return p[i].Msg < p[j].Msg
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort sorts the collection.
|
|
||||||
func (p ErrorList) Sort() {
|
|
||||||
sort.Sort(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p ErrorList) Error() string {
|
|
||||||
switch len(p) {
|
|
||||||
case 0:
|
|
||||||
return "no errors"
|
|
||||||
case 1:
|
|
||||||
return p[0].Error()
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%s (and %d more errors)", p[0], len(p)-1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Err returns an error.
|
|
||||||
func (p ErrorList) Err() error {
|
|
||||||
if len(p) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return p
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
package parser_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/d5/tengo/assert"
|
|
||||||
"github.com/d5/tengo/compiler/parser"
|
|
||||||
"github.com/d5/tengo/compiler/source"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestErrorList_Sort(t *testing.T) {
|
|
||||||
var list parser.ErrorList
|
|
||||||
list.Add(source.FilePos{Offset: 20, Line: 2, Column: 10}, "error 2")
|
|
||||||
list.Add(source.FilePos{Offset: 30, Line: 3, Column: 10}, "error 3")
|
|
||||||
list.Add(source.FilePos{Offset: 10, Line: 1, Column: 10}, "error 1")
|
|
||||||
list.Sort()
|
|
||||||
assert.Equal(t, "Parse Error: error 1\n\tat 1:10 (and 2 more errors)", list.Error())
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
package parser_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/d5/tengo/assert"
|
|
||||||
"github.com/d5/tengo/compiler/parser"
|
|
||||||
"github.com/d5/tengo/compiler/source"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestError_Error(t *testing.T) {
|
|
||||||
err := &parser.Error{Pos: source.FilePos{Offset: 10, Line: 1, Column: 10}, Msg: "test"}
|
|
||||||
assert.Equal(t, "Parse Error: test\n\tat 1:10", err.Error())
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
package parser
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/d5/tengo/compiler/ast"
|
|
||||||
"github.com/d5/tengo/compiler/source"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ParseSource parses source code 'src' and builds an AST.
|
|
||||||
func ParseSource(filename string, src []byte, trace io.Writer) (res *ast.File, err error) {
|
|
||||||
fileSet := source.NewFileSet()
|
|
||||||
file := fileSet.AddFile(filename, -1, len(src))
|
|
||||||
|
|
||||||
p := NewParser(file, src, trace)
|
|
||||||
return p.ParseFile()
|
|
||||||
}
|
|
|
@ -1,106 +0,0 @@
|
||||||
package parser_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/d5/tengo/compiler/ast"
|
|
||||||
"github.com/d5/tengo/compiler/token"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestArray(t *testing.T) {
|
|
||||||
expect(t, "[1, 2, 3]", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
exprStmt(
|
|
||||||
arrayLit(p(1, 1), p(1, 9),
|
|
||||||
intLit(1, p(1, 2)),
|
|
||||||
intLit(2, p(1, 5)),
|
|
||||||
intLit(3, p(1, 8)))))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, `
|
|
||||||
[
|
|
||||||
1,
|
|
||||||
2,
|
|
||||||
3
|
|
||||||
]`, func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
exprStmt(
|
|
||||||
arrayLit(p(2, 1), p(6, 1),
|
|
||||||
intLit(1, p(3, 2)),
|
|
||||||
intLit(2, p(4, 2)),
|
|
||||||
intLit(3, p(5, 2)))))
|
|
||||||
})
|
|
||||||
expect(t, `
|
|
||||||
[
|
|
||||||
1,
|
|
||||||
2,
|
|
||||||
3
|
|
||||||
|
|
||||||
]`, func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
exprStmt(
|
|
||||||
arrayLit(p(2, 1), p(7, 1),
|
|
||||||
intLit(1, p(3, 2)),
|
|
||||||
intLit(2, p(4, 2)),
|
|
||||||
intLit(3, p(5, 2)))))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, `[1, "foo", 12.34]`, func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
exprStmt(
|
|
||||||
arrayLit(p(1, 1), p(1, 17),
|
|
||||||
intLit(1, p(1, 2)),
|
|
||||||
stringLit("foo", p(1, 5)),
|
|
||||||
floatLit(12.34, p(1, 12)))))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, "a = [1, 2, 3]", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
assignStmt(
|
|
||||||
exprs(ident("a", p(1, 1))),
|
|
||||||
exprs(arrayLit(p(1, 5), p(1, 13),
|
|
||||||
intLit(1, p(1, 6)),
|
|
||||||
intLit(2, p(1, 9)),
|
|
||||||
intLit(3, p(1, 12)))),
|
|
||||||
token.Assign,
|
|
||||||
p(1, 3)))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, "a = [1 + 2, b * 4, [4, c]]", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
assignStmt(
|
|
||||||
exprs(ident("a", p(1, 1))),
|
|
||||||
exprs(arrayLit(p(1, 5), p(1, 26),
|
|
||||||
binaryExpr(
|
|
||||||
intLit(1, p(1, 6)),
|
|
||||||
intLit(2, p(1, 10)),
|
|
||||||
token.Add,
|
|
||||||
p(1, 8)),
|
|
||||||
binaryExpr(
|
|
||||||
ident("b", p(1, 13)),
|
|
||||||
intLit(4, p(1, 17)),
|
|
||||||
token.Mul,
|
|
||||||
p(1, 15)),
|
|
||||||
arrayLit(p(1, 20), p(1, 25),
|
|
||||||
intLit(4, p(1, 21)),
|
|
||||||
ident("c", p(1, 24))))),
|
|
||||||
token.Assign,
|
|
||||||
p(1, 3)))
|
|
||||||
})
|
|
||||||
|
|
||||||
expectError(t, `[1, 2, 3,]`)
|
|
||||||
expectError(t, `
|
|
||||||
[
|
|
||||||
1,
|
|
||||||
2,
|
|
||||||
3,
|
|
||||||
]`)
|
|
||||||
expectError(t, `
|
|
||||||
[
|
|
||||||
1,
|
|
||||||
2,
|
|
||||||
3,
|
|
||||||
|
|
||||||
]`)
|
|
||||||
expectError(t, `[1, 2, 3, ,]`)
|
|
||||||
}
|
|
|
@ -1,132 +0,0 @@
|
||||||
package parser_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/d5/tengo/compiler/ast"
|
|
||||||
"github.com/d5/tengo/compiler/token"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestAssignment(t *testing.T) {
|
|
||||||
expect(t, "a = 5", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
assignStmt(
|
|
||||||
exprs(ident("a", p(1, 1))),
|
|
||||||
exprs(intLit(5, p(1, 5))),
|
|
||||||
token.Assign,
|
|
||||||
p(1, 3)))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, "a := 5", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
assignStmt(
|
|
||||||
exprs(ident("a", p(1, 1))),
|
|
||||||
exprs(intLit(5, p(1, 6))),
|
|
||||||
token.Define,
|
|
||||||
p(1, 3)))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, "a, b = 5, 10", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
assignStmt(
|
|
||||||
exprs(
|
|
||||||
ident("a", p(1, 1)),
|
|
||||||
ident("b", p(1, 4))),
|
|
||||||
exprs(
|
|
||||||
intLit(5, p(1, 8)),
|
|
||||||
intLit(10, p(1, 11))),
|
|
||||||
token.Assign,
|
|
||||||
p(1, 6)))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, "a, b := 5, 10", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
assignStmt(
|
|
||||||
exprs(
|
|
||||||
ident("a", p(1, 1)),
|
|
||||||
ident("b", p(1, 4))),
|
|
||||||
exprs(
|
|
||||||
intLit(5, p(1, 9)),
|
|
||||||
intLit(10, p(1, 12))),
|
|
||||||
token.Define,
|
|
||||||
p(1, 6)))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, "a, b = a + 2, b - 8", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
assignStmt(
|
|
||||||
exprs(
|
|
||||||
ident("a", p(1, 1)),
|
|
||||||
ident("b", p(1, 4))),
|
|
||||||
exprs(
|
|
||||||
binaryExpr(
|
|
||||||
ident("a", p(1, 8)),
|
|
||||||
intLit(2, p(1, 12)),
|
|
||||||
token.Add,
|
|
||||||
p(1, 10)),
|
|
||||||
binaryExpr(
|
|
||||||
ident("b", p(1, 15)),
|
|
||||||
intLit(8, p(1, 19)),
|
|
||||||
token.Sub,
|
|
||||||
p(1, 17))),
|
|
||||||
token.Assign,
|
|
||||||
p(1, 6)))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, "a = [1, 2, 3]", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
assignStmt(
|
|
||||||
exprs(ident("a", p(1, 1))),
|
|
||||||
exprs(arrayLit(p(1, 5), p(1, 13),
|
|
||||||
intLit(1, p(1, 6)),
|
|
||||||
intLit(2, p(1, 9)),
|
|
||||||
intLit(3, p(1, 12)))),
|
|
||||||
token.Assign,
|
|
||||||
p(1, 3)))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, "a = [1 + 2, b * 4, [4, c]]", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
assignStmt(
|
|
||||||
exprs(ident("a", p(1, 1))),
|
|
||||||
exprs(arrayLit(p(1, 5), p(1, 26),
|
|
||||||
binaryExpr(
|
|
||||||
intLit(1, p(1, 6)),
|
|
||||||
intLit(2, p(1, 10)),
|
|
||||||
token.Add,
|
|
||||||
p(1, 8)),
|
|
||||||
binaryExpr(
|
|
||||||
ident("b", p(1, 13)),
|
|
||||||
intLit(4, p(1, 17)),
|
|
||||||
token.Mul,
|
|
||||||
p(1, 15)),
|
|
||||||
arrayLit(p(1, 20), p(1, 25),
|
|
||||||
intLit(4, p(1, 21)),
|
|
||||||
ident("c", p(1, 24))))),
|
|
||||||
token.Assign,
|
|
||||||
p(1, 3)))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, "a += 5", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
assignStmt(
|
|
||||||
exprs(ident("a", p(1, 1))),
|
|
||||||
exprs(intLit(5, p(1, 6))),
|
|
||||||
token.AddAssign,
|
|
||||||
p(1, 3)))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, "a *= 5 + 10", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
assignStmt(
|
|
||||||
exprs(ident("a", p(1, 1))),
|
|
||||||
exprs(
|
|
||||||
binaryExpr(
|
|
||||||
intLit(5, p(1, 6)),
|
|
||||||
intLit(10, p(1, 10)),
|
|
||||||
token.Add,
|
|
||||||
p(1, 8))),
|
|
||||||
token.MulAssign,
|
|
||||||
p(1, 3)))
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,41 +0,0 @@
|
||||||
package parser_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/d5/tengo/compiler/ast"
|
|
||||||
"github.com/d5/tengo/compiler/token"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestBoolean(t *testing.T) {
|
|
||||||
expect(t, "true", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
exprStmt(
|
|
||||||
boolLit(true, p(1, 1))))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, "false", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
exprStmt(
|
|
||||||
boolLit(false, p(1, 1))))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, "true != false", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
exprStmt(
|
|
||||||
binaryExpr(
|
|
||||||
boolLit(true, p(1, 1)),
|
|
||||||
boolLit(false, p(1, 9)),
|
|
||||||
token.NotEqual,
|
|
||||||
p(1, 6))))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, "!false", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
exprStmt(
|
|
||||||
unaryExpr(
|
|
||||||
boolLit(false, p(1, 2)),
|
|
||||||
token.Not,
|
|
||||||
p(1, 1))))
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,144 +0,0 @@
|
||||||
package parser_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/d5/tengo/compiler/ast"
|
|
||||||
"github.com/d5/tengo/compiler/token"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestCall(t *testing.T) {
|
|
||||||
expect(t, "add(1, 2, 3)", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
exprStmt(
|
|
||||||
callExpr(
|
|
||||||
ident("add", p(1, 1)),
|
|
||||||
p(1, 4), p(1, 12),
|
|
||||||
intLit(1, p(1, 5)),
|
|
||||||
intLit(2, p(1, 8)),
|
|
||||||
intLit(3, p(1, 11)))))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, "a = add(1, 2, 3)", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
assignStmt(
|
|
||||||
exprs(
|
|
||||||
ident("a", p(1, 1))),
|
|
||||||
exprs(
|
|
||||||
callExpr(
|
|
||||||
ident("add", p(1, 5)),
|
|
||||||
p(1, 8), p(1, 16),
|
|
||||||
intLit(1, p(1, 9)),
|
|
||||||
intLit(2, p(1, 12)),
|
|
||||||
intLit(3, p(1, 15)))),
|
|
||||||
token.Assign,
|
|
||||||
p(1, 3)))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, "a, b = add(1, 2, 3)", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
assignStmt(
|
|
||||||
exprs(
|
|
||||||
ident("a", p(1, 1)),
|
|
||||||
ident("b", p(1, 4))),
|
|
||||||
exprs(
|
|
||||||
callExpr(
|
|
||||||
ident("add", p(1, 8)),
|
|
||||||
p(1, 11), p(1, 19),
|
|
||||||
intLit(1, p(1, 12)),
|
|
||||||
intLit(2, p(1, 15)),
|
|
||||||
intLit(3, p(1, 18)))),
|
|
||||||
token.Assign,
|
|
||||||
p(1, 6)))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, "add(a + 1, 2 * 1, (b + c))", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
exprStmt(
|
|
||||||
callExpr(
|
|
||||||
ident("add", p(1, 1)),
|
|
||||||
p(1, 4), p(1, 26),
|
|
||||||
binaryExpr(
|
|
||||||
ident("a", p(1, 5)),
|
|
||||||
intLit(1, p(1, 9)),
|
|
||||||
token.Add,
|
|
||||||
p(1, 7)),
|
|
||||||
binaryExpr(
|
|
||||||
intLit(2, p(1, 12)),
|
|
||||||
intLit(1, p(1, 16)),
|
|
||||||
token.Mul,
|
|
||||||
p(1, 14)),
|
|
||||||
parenExpr(
|
|
||||||
binaryExpr(
|
|
||||||
ident("b", p(1, 20)),
|
|
||||||
ident("c", p(1, 24)),
|
|
||||||
token.Add,
|
|
||||||
p(1, 22)),
|
|
||||||
p(1, 19), p(1, 25)))))
|
|
||||||
})
|
|
||||||
|
|
||||||
expectString(t, "a + add(b * c) + d", "((a + add((b * c))) + d)")
|
|
||||||
expectString(t, "add(a, b, 1, 2 * 3, 4 + 5, add(6, 7 * 8))", "add(a, b, 1, (2 * 3), (4 + 5), add(6, (7 * 8)))")
|
|
||||||
expectString(t, "f1(a) + f2(b) * f3(c)", "(f1(a) + (f2(b) * f3(c)))")
|
|
||||||
expectString(t, "(f1(a) + f2(b)) * f3(c)", "(((f1(a) + f2(b))) * f3(c))")
|
|
||||||
|
|
||||||
expect(t, "func(a, b) { a + b }(1, 2)", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
exprStmt(
|
|
||||||
callExpr(
|
|
||||||
funcLit(
|
|
||||||
funcType(
|
|
||||||
identList(
|
|
||||||
p(1, 5), p(1, 10),
|
|
||||||
false,
|
|
||||||
ident("a", p(1, 6)),
|
|
||||||
ident("b", p(1, 9))),
|
|
||||||
p(1, 1)),
|
|
||||||
blockStmt(
|
|
||||||
p(1, 12), p(1, 20),
|
|
||||||
exprStmt(
|
|
||||||
binaryExpr(
|
|
||||||
ident("a", p(1, 14)),
|
|
||||||
ident("b", p(1, 18)),
|
|
||||||
token.Add,
|
|
||||||
p(1, 16))))),
|
|
||||||
p(1, 21), p(1, 26),
|
|
||||||
intLit(1, p(1, 22)),
|
|
||||||
intLit(2, p(1, 25)))))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, `a.b()`, func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
exprStmt(
|
|
||||||
callExpr(
|
|
||||||
selectorExpr(
|
|
||||||
ident("a", p(1, 1)),
|
|
||||||
stringLit("b", p(1, 3))),
|
|
||||||
p(1, 4), p(1, 5))))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, `a.b.c()`, func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
exprStmt(
|
|
||||||
callExpr(
|
|
||||||
selectorExpr(
|
|
||||||
selectorExpr(
|
|
||||||
ident("a", p(1, 1)),
|
|
||||||
stringLit("b", p(1, 3))),
|
|
||||||
stringLit("c", p(1, 5))),
|
|
||||||
p(1, 6), p(1, 7))))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, `a["b"].c()`, func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
exprStmt(
|
|
||||||
callExpr(
|
|
||||||
selectorExpr(
|
|
||||||
indexExpr(
|
|
||||||
ident("a", p(1, 1)),
|
|
||||||
stringLit("b", p(1, 3)),
|
|
||||||
p(1, 2), p(1, 6)),
|
|
||||||
stringLit("c", p(1, 8))),
|
|
||||||
p(1, 9), p(1, 10))))
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
package parser_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/d5/tengo/compiler/ast"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestChar(t *testing.T) {
|
|
||||||
expect(t, `'A'`, func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
exprStmt(
|
|
||||||
charLit('A', 1)))
|
|
||||||
})
|
|
||||||
expect(t, `'九'`, func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
exprStmt(
|
|
||||||
charLit('九', 1)))
|
|
||||||
})
|
|
||||||
|
|
||||||
expectError(t, `''`)
|
|
||||||
expectError(t, `'AB'`)
|
|
||||||
expectError(t, `'A九'`)
|
|
||||||
}
|
|
|
@ -1,48 +0,0 @@
|
||||||
package parser_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/d5/tengo/compiler/ast"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestCondExpr(t *testing.T) {
|
|
||||||
expect(t, "a ? b : c", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
exprStmt(
|
|
||||||
condExpr(
|
|
||||||
ident("a", p(1, 1)),
|
|
||||||
ident("b", p(1, 5)),
|
|
||||||
ident("c", p(1, 9)),
|
|
||||||
p(1, 3),
|
|
||||||
p(1, 7))))
|
|
||||||
})
|
|
||||||
expect(t, `a ?
|
|
||||||
b :
|
|
||||||
c`, func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
exprStmt(
|
|
||||||
condExpr(
|
|
||||||
ident("a", p(1, 1)),
|
|
||||||
ident("b", p(1, 5)),
|
|
||||||
ident("c", p(1, 9)),
|
|
||||||
p(1, 3),
|
|
||||||
p(1, 7))))
|
|
||||||
})
|
|
||||||
|
|
||||||
expectString(t, `a ? b : c`, "(a ? b : c)")
|
|
||||||
expectString(t, `a + b ? c - d : e * f`, "((a + b) ? (c - d) : (e * f))")
|
|
||||||
expectString(t, `a == b ? c + (d / e) : f ? g : h + i`, "((a == b) ? (c + ((d / e))) : (f ? g : (h + i)))")
|
|
||||||
expectString(t, `(a + b) ? (c - d) : (e * f)`, "(((a + b)) ? ((c - d)) : ((e * f)))")
|
|
||||||
expectString(t, `a + (b ? c : d) - e`, "((a + ((b ? c : d))) - e)")
|
|
||||||
expectString(t, `a ? b ? c : d : e`, "(a ? (b ? c : d) : e)")
|
|
||||||
expectString(t, `a := b ? c : d`, "a := (b ? c : d)")
|
|
||||||
expectString(t, `x := a ? b ? c : d : e`, "x := (a ? (b ? c : d) : e)")
|
|
||||||
|
|
||||||
// ? : should be at the end of each line if it's multi-line
|
|
||||||
expectError(t, `a
|
|
||||||
? b
|
|
||||||
: c`)
|
|
||||||
expectError(t, `a ? (b : e)`)
|
|
||||||
expectError(t, `(a ? b) : e`)
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
package parser_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/d5/tengo/compiler/ast"
|
|
||||||
"github.com/d5/tengo/compiler/token"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestError(t *testing.T) {
|
|
||||||
expect(t, `error(1234)`, func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
exprStmt(
|
|
||||||
errorExpr(p(1, 1), intLit(1234, p(1, 7)), p(1, 6), p(1, 11))))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, `err1 := error("some error")`, func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
assignStmt(
|
|
||||||
exprs(ident("err1", p(1, 1))),
|
|
||||||
exprs(errorExpr(p(1, 9), stringLit("some error", p(1, 15)), p(1, 14), p(1, 27))),
|
|
||||||
token.Define, p(1, 6)))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, `return error("some error")`, func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
returnStmt(p(1, 1),
|
|
||||||
errorExpr(p(1, 8), stringLit("some error", p(1, 14)), p(1, 13), p(1, 26))))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, `return error("some" + "error")`, func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
returnStmt(p(1, 1),
|
|
||||||
errorExpr(p(1, 8),
|
|
||||||
binaryExpr(
|
|
||||||
stringLit("some", p(1, 14)),
|
|
||||||
stringLit("error", p(1, 23)),
|
|
||||||
token.Add, p(1, 21)),
|
|
||||||
p(1, 13), p(1, 30))))
|
|
||||||
})
|
|
||||||
|
|
||||||
expectError(t, `error()`) // must have a value
|
|
||||||
}
|
|
|
@ -1,66 +0,0 @@
|
||||||
package parser_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/d5/tengo/compiler/ast"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestForIn(t *testing.T) {
|
|
||||||
expect(t, "for x in y {}", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
forInStmt(
|
|
||||||
ident("_", p(1, 5)),
|
|
||||||
ident("x", p(1, 5)),
|
|
||||||
ident("y", p(1, 10)),
|
|
||||||
blockStmt(p(1, 12), p(1, 13)),
|
|
||||||
p(1, 1)))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, "for _ in y {}", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
forInStmt(
|
|
||||||
ident("_", p(1, 5)),
|
|
||||||
ident("_", p(1, 5)),
|
|
||||||
ident("y", p(1, 10)),
|
|
||||||
blockStmt(p(1, 12), p(1, 13)),
|
|
||||||
p(1, 1)))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, "for x in [1, 2, 3] {}", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
forInStmt(
|
|
||||||
ident("_", p(1, 5)),
|
|
||||||
ident("x", p(1, 5)),
|
|
||||||
arrayLit(
|
|
||||||
p(1, 10), p(1, 18),
|
|
||||||
intLit(1, p(1, 11)),
|
|
||||||
intLit(2, p(1, 14)),
|
|
||||||
intLit(3, p(1, 17))),
|
|
||||||
blockStmt(p(1, 20), p(1, 21)),
|
|
||||||
p(1, 1)))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, "for x, y in z {}", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
forInStmt(
|
|
||||||
ident("x", p(1, 5)),
|
|
||||||
ident("y", p(1, 8)),
|
|
||||||
ident("z", p(1, 13)),
|
|
||||||
blockStmt(p(1, 15), p(1, 16)),
|
|
||||||
p(1, 1)))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, "for x, y in {k1: 1, k2: 2} {}", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
forInStmt(
|
|
||||||
ident("x", p(1, 5)),
|
|
||||||
ident("y", p(1, 8)),
|
|
||||||
mapLit(
|
|
||||||
p(1, 13), p(1, 26),
|
|
||||||
mapElementLit("k1", p(1, 14), p(1, 16), intLit(1, p(1, 18))),
|
|
||||||
mapElementLit("k2", p(1, 21), p(1, 23), intLit(2, p(1, 25)))),
|
|
||||||
blockStmt(p(1, 28), p(1, 29)),
|
|
||||||
p(1, 1)))
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,118 +0,0 @@
|
||||||
package parser_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/d5/tengo/compiler/ast"
|
|
||||||
"github.com/d5/tengo/compiler/token"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestFor(t *testing.T) {
|
|
||||||
expect(t, "for {}", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
forStmt(nil, nil, nil, blockStmt(p(1, 5), p(1, 6)), p(1, 1)))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, "for a == 5 {}", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
forStmt(
|
|
||||||
nil,
|
|
||||||
binaryExpr(
|
|
||||||
ident("a", p(1, 5)),
|
|
||||||
intLit(5, p(1, 10)),
|
|
||||||
token.Equal,
|
|
||||||
p(1, 7)),
|
|
||||||
nil,
|
|
||||||
blockStmt(p(1, 12), p(1, 13)),
|
|
||||||
p(1, 1)))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, "for a := 0; a == 5; {}", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
forStmt(
|
|
||||||
assignStmt(
|
|
||||||
exprs(ident("a", p(1, 5))),
|
|
||||||
exprs(intLit(0, p(1, 10))),
|
|
||||||
token.Define, p(1, 7)),
|
|
||||||
binaryExpr(
|
|
||||||
ident("a", p(1, 13)),
|
|
||||||
intLit(5, p(1, 18)),
|
|
||||||
token.Equal,
|
|
||||||
p(1, 15)),
|
|
||||||
nil,
|
|
||||||
blockStmt(p(1, 22), p(1, 23)),
|
|
||||||
p(1, 1)))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, "for a := 0; a < 5; a++ {}", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
forStmt(
|
|
||||||
assignStmt(
|
|
||||||
exprs(ident("a", p(1, 5))),
|
|
||||||
exprs(intLit(0, p(1, 10))),
|
|
||||||
token.Define, p(1, 7)),
|
|
||||||
binaryExpr(
|
|
||||||
ident("a", p(1, 13)),
|
|
||||||
intLit(5, p(1, 17)),
|
|
||||||
token.Less,
|
|
||||||
p(1, 15)),
|
|
||||||
incDecStmt(
|
|
||||||
ident("a", p(1, 20)),
|
|
||||||
token.Inc, p(1, 21)),
|
|
||||||
blockStmt(p(1, 24), p(1, 25)),
|
|
||||||
p(1, 1)))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, "for ; a < 5; a++ {}", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
forStmt(
|
|
||||||
nil,
|
|
||||||
binaryExpr(
|
|
||||||
ident("a", p(1, 7)),
|
|
||||||
intLit(5, p(1, 11)),
|
|
||||||
token.Less,
|
|
||||||
p(1, 9)),
|
|
||||||
incDecStmt(
|
|
||||||
ident("a", p(1, 14)),
|
|
||||||
token.Inc, p(1, 15)),
|
|
||||||
blockStmt(p(1, 18), p(1, 19)),
|
|
||||||
p(1, 1)))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, "for a := 0; ; a++ {}", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
forStmt(
|
|
||||||
assignStmt(
|
|
||||||
exprs(ident("a", p(1, 5))),
|
|
||||||
exprs(intLit(0, p(1, 10))),
|
|
||||||
token.Define, p(1, 7)),
|
|
||||||
nil,
|
|
||||||
incDecStmt(
|
|
||||||
ident("a", p(1, 15)),
|
|
||||||
token.Inc, p(1, 16)),
|
|
||||||
blockStmt(p(1, 19), p(1, 20)),
|
|
||||||
p(1, 1)))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, "for a == 5 && b != 4 {}", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
forStmt(
|
|
||||||
nil,
|
|
||||||
binaryExpr(
|
|
||||||
binaryExpr(
|
|
||||||
ident("a", p(1, 5)),
|
|
||||||
intLit(5, p(1, 10)),
|
|
||||||
token.Equal,
|
|
||||||
p(1, 7)),
|
|
||||||
binaryExpr(
|
|
||||||
ident("b", p(1, 15)),
|
|
||||||
intLit(4, p(1, 20)),
|
|
||||||
token.NotEqual,
|
|
||||||
p(1, 17)),
|
|
||||||
token.LAnd,
|
|
||||||
p(1, 12)),
|
|
||||||
nil,
|
|
||||||
blockStmt(p(1, 22), p(1, 23)),
|
|
||||||
p(1, 1)))
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,86 +0,0 @@
|
||||||
package parser_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/d5/tengo/compiler/ast"
|
|
||||||
"github.com/d5/tengo/compiler/token"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestFunction(t *testing.T) {
|
|
||||||
expect(t, "a = func(b, c, d) { return d }", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
assignStmt(
|
|
||||||
exprs(
|
|
||||||
ident("a", p(1, 1))),
|
|
||||||
exprs(
|
|
||||||
funcLit(
|
|
||||||
funcType(
|
|
||||||
identList(p(1, 9), p(1, 17), false,
|
|
||||||
ident("b", p(1, 10)),
|
|
||||||
ident("c", p(1, 13)),
|
|
||||||
ident("d", p(1, 16))),
|
|
||||||
p(1, 5)),
|
|
||||||
blockStmt(p(1, 19), p(1, 30),
|
|
||||||
returnStmt(p(1, 21), ident("d", p(1, 28)))))),
|
|
||||||
token.Assign,
|
|
||||||
p(1, 3)))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVariableFunction(t *testing.T) {
|
|
||||||
expect(t, "a = func(...args) { return args }", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
assignStmt(
|
|
||||||
exprs(
|
|
||||||
ident("a", p(1, 1))),
|
|
||||||
exprs(
|
|
||||||
funcLit(
|
|
||||||
funcType(
|
|
||||||
identList(
|
|
||||||
p(1, 9), p(1, 17),
|
|
||||||
true,
|
|
||||||
ident("args", p(1, 13)),
|
|
||||||
), p(1, 5)),
|
|
||||||
blockStmt(p(1, 19), p(1, 33),
|
|
||||||
returnStmt(p(1, 21),
|
|
||||||
ident("args", p(1, 28)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
token.Assign,
|
|
||||||
p(1, 3)))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVariableFunctionWithArgs(t *testing.T) {
|
|
||||||
expect(t, "a = func(x, y, ...z) { return z }", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
assignStmt(
|
|
||||||
exprs(
|
|
||||||
ident("a", p(1, 1))),
|
|
||||||
exprs(
|
|
||||||
funcLit(
|
|
||||||
funcType(
|
|
||||||
identList(
|
|
||||||
p(1, 9), p(1, 20),
|
|
||||||
true,
|
|
||||||
ident("x", p(1, 10)),
|
|
||||||
ident("y", p(1, 13)),
|
|
||||||
ident("z", p(1, 19)),
|
|
||||||
), p(1, 5)),
|
|
||||||
blockStmt(p(1, 22), p(1, 33),
|
|
||||||
returnStmt(p(1, 24),
|
|
||||||
ident("z", p(1, 31)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
token.Assign,
|
|
||||||
p(1, 3)))
|
|
||||||
})
|
|
||||||
|
|
||||||
expectError(t, "a = func(x, y, ...z, invalid) { return z }")
|
|
||||||
expectError(t, "a = func(...args, invalid) { return args }")
|
|
||||||
}
|
|
|
@ -1,215 +0,0 @@
|
||||||
package parser_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/d5/tengo/compiler/ast"
|
|
||||||
"github.com/d5/tengo/compiler/token"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestIf(t *testing.T) {
|
|
||||||
expect(t, "if a == 5 {}", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
ifStmt(
|
|
||||||
nil,
|
|
||||||
binaryExpr(
|
|
||||||
ident("a", p(1, 4)),
|
|
||||||
intLit(5, p(1, 9)),
|
|
||||||
token.Equal,
|
|
||||||
p(1, 6)),
|
|
||||||
blockStmt(
|
|
||||||
p(1, 11), p(1, 12)),
|
|
||||||
nil,
|
|
||||||
p(1, 1)))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, "if a == 5 && b != 3 {}", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
ifStmt(
|
|
||||||
nil,
|
|
||||||
binaryExpr(
|
|
||||||
binaryExpr(
|
|
||||||
ident("a", p(1, 4)),
|
|
||||||
intLit(5, p(1, 9)),
|
|
||||||
token.Equal,
|
|
||||||
p(1, 6)),
|
|
||||||
binaryExpr(
|
|
||||||
ident("b", p(1, 14)),
|
|
||||||
intLit(3, p(1, 19)),
|
|
||||||
token.NotEqual,
|
|
||||||
p(1, 16)),
|
|
||||||
token.LAnd,
|
|
||||||
p(1, 11)),
|
|
||||||
blockStmt(
|
|
||||||
p(1, 21), p(1, 22)),
|
|
||||||
nil,
|
|
||||||
p(1, 1)))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, "if a == 5 { a = 3; a = 1 }", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
ifStmt(
|
|
||||||
nil,
|
|
||||||
binaryExpr(
|
|
||||||
ident("a", p(1, 4)),
|
|
||||||
intLit(5, p(1, 9)),
|
|
||||||
token.Equal,
|
|
||||||
p(1, 6)),
|
|
||||||
blockStmt(
|
|
||||||
p(1, 11), p(1, 26),
|
|
||||||
assignStmt(
|
|
||||||
exprs(ident("a", p(1, 13))),
|
|
||||||
exprs(intLit(3, p(1, 17))),
|
|
||||||
token.Assign,
|
|
||||||
p(1, 15)),
|
|
||||||
assignStmt(
|
|
||||||
exprs(ident("a", p(1, 20))),
|
|
||||||
exprs(intLit(1, p(1, 24))),
|
|
||||||
token.Assign,
|
|
||||||
p(1, 22))),
|
|
||||||
nil,
|
|
||||||
p(1, 1)))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, "if a == 5 { a = 3; a = 1 } else { a = 2; a = 4 }", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
ifStmt(
|
|
||||||
nil,
|
|
||||||
binaryExpr(
|
|
||||||
ident("a", p(1, 4)),
|
|
||||||
intLit(5, p(1, 9)),
|
|
||||||
token.Equal,
|
|
||||||
p(1, 6)),
|
|
||||||
blockStmt(
|
|
||||||
p(1, 11), p(1, 26),
|
|
||||||
assignStmt(
|
|
||||||
exprs(ident("a", p(1, 13))),
|
|
||||||
exprs(intLit(3, p(1, 17))),
|
|
||||||
token.Assign,
|
|
||||||
p(1, 15)),
|
|
||||||
assignStmt(
|
|
||||||
exprs(ident("a", p(1, 20))),
|
|
||||||
exprs(intLit(1, p(1, 24))),
|
|
||||||
token.Assign,
|
|
||||||
p(1, 22))),
|
|
||||||
blockStmt(
|
|
||||||
p(1, 33), p(1, 48),
|
|
||||||
assignStmt(
|
|
||||||
exprs(ident("a", p(1, 35))),
|
|
||||||
exprs(intLit(2, p(1, 39))),
|
|
||||||
token.Assign,
|
|
||||||
p(1, 37)),
|
|
||||||
assignStmt(
|
|
||||||
exprs(ident("a", p(1, 42))),
|
|
||||||
exprs(intLit(4, p(1, 46))),
|
|
||||||
token.Assign,
|
|
||||||
p(1, 44))),
|
|
||||||
p(1, 1)))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, `
|
|
||||||
if a == 5 {
|
|
||||||
b = 3
|
|
||||||
c = 1
|
|
||||||
} else if d == 3 {
|
|
||||||
e = 8
|
|
||||||
f = 3
|
|
||||||
} else {
|
|
||||||
g = 2
|
|
||||||
h = 4
|
|
||||||
}`, func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
ifStmt(
|
|
||||||
nil,
|
|
||||||
binaryExpr(
|
|
||||||
ident("a", p(2, 4)),
|
|
||||||
intLit(5, p(2, 9)),
|
|
||||||
token.Equal,
|
|
||||||
p(2, 6)),
|
|
||||||
blockStmt(
|
|
||||||
p(2, 11), p(5, 1),
|
|
||||||
assignStmt(
|
|
||||||
exprs(ident("b", p(3, 2))),
|
|
||||||
exprs(intLit(3, p(3, 6))),
|
|
||||||
token.Assign,
|
|
||||||
p(3, 4)),
|
|
||||||
assignStmt(
|
|
||||||
exprs(ident("c", p(4, 2))),
|
|
||||||
exprs(intLit(1, p(4, 6))),
|
|
||||||
token.Assign,
|
|
||||||
p(4, 4))),
|
|
||||||
ifStmt(
|
|
||||||
nil,
|
|
||||||
binaryExpr(
|
|
||||||
ident("d", p(5, 11)),
|
|
||||||
intLit(3, p(5, 16)),
|
|
||||||
token.Equal,
|
|
||||||
p(5, 13)),
|
|
||||||
blockStmt(
|
|
||||||
p(5, 18), p(8, 1),
|
|
||||||
assignStmt(
|
|
||||||
exprs(ident("e", p(6, 2))),
|
|
||||||
exprs(intLit(8, p(6, 6))),
|
|
||||||
token.Assign,
|
|
||||||
p(6, 4)),
|
|
||||||
assignStmt(
|
|
||||||
exprs(ident("f", p(7, 2))),
|
|
||||||
exprs(intLit(3, p(7, 6))),
|
|
||||||
token.Assign,
|
|
||||||
p(7, 4))),
|
|
||||||
blockStmt(
|
|
||||||
p(8, 8), p(11, 1),
|
|
||||||
assignStmt(
|
|
||||||
exprs(ident("g", p(9, 2))),
|
|
||||||
exprs(intLit(2, p(9, 6))),
|
|
||||||
token.Assign,
|
|
||||||
p(9, 4)),
|
|
||||||
assignStmt(
|
|
||||||
exprs(ident("h", p(10, 2))),
|
|
||||||
exprs(intLit(4, p(10, 6))),
|
|
||||||
token.Assign,
|
|
||||||
p(10, 4))),
|
|
||||||
p(5, 8)),
|
|
||||||
p(2, 1)))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, "if a := 3; a < b {}", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
ifStmt(
|
|
||||||
assignStmt(
|
|
||||||
exprs(ident("a", p(1, 4))),
|
|
||||||
exprs(intLit(3, p(1, 9))),
|
|
||||||
token.Define, p(1, 6)),
|
|
||||||
binaryExpr(
|
|
||||||
ident("a", p(1, 12)),
|
|
||||||
ident("b", p(1, 16)),
|
|
||||||
token.Less, p(1, 14)),
|
|
||||||
blockStmt(
|
|
||||||
p(1, 18), p(1, 19)),
|
|
||||||
nil,
|
|
||||||
p(1, 1)))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, "if a++; a < b {}", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
ifStmt(
|
|
||||||
incDecStmt(ident("a", p(1, 4)), token.Inc, p(1, 5)),
|
|
||||||
binaryExpr(
|
|
||||||
ident("a", p(1, 9)),
|
|
||||||
ident("b", p(1, 13)),
|
|
||||||
token.Less, p(1, 11)),
|
|
||||||
blockStmt(
|
|
||||||
p(1, 15), p(1, 16)),
|
|
||||||
nil,
|
|
||||||
p(1, 1)))
|
|
||||||
})
|
|
||||||
|
|
||||||
expectError(t, `if {}`)
|
|
||||||
expectError(t, `if a == b { } else a != b { }`)
|
|
||||||
expectError(t, `if a == b { } else if { }`)
|
|
||||||
expectError(t, `else { }`)
|
|
||||||
expectError(t, `if ; {}`)
|
|
||||||
expectError(t, `if a := 3; {}`)
|
|
||||||
expectError(t, `if ; a < 3 {}`)
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,46 +0,0 @@
|
||||||
package parser_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/d5/tengo/compiler/ast"
|
|
||||||
"github.com/d5/tengo/compiler/token"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestImport(t *testing.T) {
|
|
||||||
expect(t, `a := import("mod1")`, func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
assignStmt(
|
|
||||||
exprs(ident("a", p(1, 1))),
|
|
||||||
exprs(importExpr("mod1", p(1, 6))),
|
|
||||||
token.Define, p(1, 3)))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, `import("mod1").var1`, func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
exprStmt(
|
|
||||||
selectorExpr(
|
|
||||||
importExpr("mod1", p(1, 1)),
|
|
||||||
stringLit("var1", p(1, 16)))))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, `import("mod1").func1()`, func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
exprStmt(
|
|
||||||
callExpr(
|
|
||||||
selectorExpr(
|
|
||||||
importExpr("mod1", p(1, 1)),
|
|
||||||
stringLit("func1", p(1, 16))),
|
|
||||||
p(1, 21), p(1, 22))))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, `for x, y in import("mod1") {}`, func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
forInStmt(
|
|
||||||
ident("x", p(1, 5)),
|
|
||||||
ident("y", p(1, 8)),
|
|
||||||
importExpr("mod1", p(1, 13)),
|
|
||||||
blockStmt(p(1, 28), p(1, 29)),
|
|
||||||
p(1, 1)))
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,98 +0,0 @@
|
||||||
package parser_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/d5/tengo/compiler/ast"
|
|
||||||
"github.com/d5/tengo/compiler/token"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestIndex(t *testing.T) {
|
|
||||||
expect(t, "[1, 2, 3][1]", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
exprStmt(
|
|
||||||
indexExpr(
|
|
||||||
arrayLit(p(1, 1), p(1, 9),
|
|
||||||
intLit(1, p(1, 2)),
|
|
||||||
intLit(2, p(1, 5)),
|
|
||||||
intLit(3, p(1, 8))),
|
|
||||||
intLit(1, p(1, 11)),
|
|
||||||
p(1, 10), p(1, 12))))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, "[1, 2, 3][5 - a]", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
exprStmt(
|
|
||||||
indexExpr(
|
|
||||||
arrayLit(p(1, 1), p(1, 9),
|
|
||||||
intLit(1, p(1, 2)),
|
|
||||||
intLit(2, p(1, 5)),
|
|
||||||
intLit(3, p(1, 8))),
|
|
||||||
binaryExpr(
|
|
||||||
intLit(5, p(1, 11)),
|
|
||||||
ident("a", p(1, 15)),
|
|
||||||
token.Sub,
|
|
||||||
p(1, 13)),
|
|
||||||
p(1, 10), p(1, 16))))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, "[1, 2, 3][5 : a]", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
exprStmt(
|
|
||||||
sliceExpr(
|
|
||||||
arrayLit(p(1, 1), p(1, 9),
|
|
||||||
intLit(1, p(1, 2)),
|
|
||||||
intLit(2, p(1, 5)),
|
|
||||||
intLit(3, p(1, 8))),
|
|
||||||
intLit(5, p(1, 11)),
|
|
||||||
ident("a", p(1, 15)),
|
|
||||||
p(1, 10), p(1, 16))))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, "[1, 2, 3][a + 3 : b - 8]", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
exprStmt(
|
|
||||||
sliceExpr(
|
|
||||||
arrayLit(p(1, 1), p(1, 9),
|
|
||||||
intLit(1, p(1, 2)),
|
|
||||||
intLit(2, p(1, 5)),
|
|
||||||
intLit(3, p(1, 8))),
|
|
||||||
binaryExpr(
|
|
||||||
ident("a", p(1, 11)),
|
|
||||||
intLit(3, p(1, 15)),
|
|
||||||
token.Add,
|
|
||||||
p(1, 13)),
|
|
||||||
binaryExpr(
|
|
||||||
ident("b", p(1, 19)),
|
|
||||||
intLit(8, p(1, 23)),
|
|
||||||
token.Sub,
|
|
||||||
p(1, 21)),
|
|
||||||
p(1, 10), p(1, 24))))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, `{a: 1, b: 2}["b"]`, func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
exprStmt(
|
|
||||||
indexExpr(
|
|
||||||
mapLit(p(1, 1), p(1, 12),
|
|
||||||
mapElementLit("a", p(1, 2), p(1, 3), intLit(1, p(1, 5))),
|
|
||||||
mapElementLit("b", p(1, 8), p(1, 9), intLit(2, p(1, 11)))),
|
|
||||||
stringLit("b", p(1, 14)),
|
|
||||||
p(1, 13), p(1, 17))))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, `{a: 1, b: 2}[a + b]`, func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
exprStmt(
|
|
||||||
indexExpr(
|
|
||||||
mapLit(p(1, 1), p(1, 12),
|
|
||||||
mapElementLit("a", p(1, 2), p(1, 3), intLit(1, p(1, 5))),
|
|
||||||
mapElementLit("b", p(1, 8), p(1, 9), intLit(2, p(1, 11)))),
|
|
||||||
binaryExpr(
|
|
||||||
ident("a", p(1, 14)),
|
|
||||||
ident("b", p(1, 18)),
|
|
||||||
token.Add,
|
|
||||||
p(1, 16)),
|
|
||||||
p(1, 13), p(1, 19))))
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,54 +0,0 @@
|
||||||
package parser_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/d5/tengo/compiler/ast"
|
|
||||||
"github.com/d5/tengo/compiler/token"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestLogical(t *testing.T) {
|
|
||||||
expect(t, "a && 5 || true", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
exprStmt(
|
|
||||||
binaryExpr(
|
|
||||||
binaryExpr(
|
|
||||||
ident("a", p(1, 1)),
|
|
||||||
intLit(5, p(1, 6)),
|
|
||||||
token.LAnd,
|
|
||||||
p(1, 3)),
|
|
||||||
boolLit(true, p(1, 11)),
|
|
||||||
token.LOr,
|
|
||||||
p(1, 8))))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, "a || 5 && true", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
exprStmt(
|
|
||||||
binaryExpr(
|
|
||||||
ident("a", p(1, 1)),
|
|
||||||
binaryExpr(
|
|
||||||
intLit(5, p(1, 6)),
|
|
||||||
boolLit(true, p(1, 11)),
|
|
||||||
token.LAnd,
|
|
||||||
p(1, 8)),
|
|
||||||
token.LOr,
|
|
||||||
p(1, 3))))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, "a && (5 || true)", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
exprStmt(
|
|
||||||
binaryExpr(
|
|
||||||
ident("a", p(1, 1)),
|
|
||||||
parenExpr(
|
|
||||||
binaryExpr(
|
|
||||||
intLit(5, p(1, 7)),
|
|
||||||
boolLit(true, p(1, 12)),
|
|
||||||
token.LOr,
|
|
||||||
p(1, 9)),
|
|
||||||
p(1, 6), p(1, 16)),
|
|
||||||
token.LAnd,
|
|
||||||
p(1, 3))))
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,80 +0,0 @@
|
||||||
package parser_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/d5/tengo/compiler/ast"
|
|
||||||
"github.com/d5/tengo/compiler/token"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestMap(t *testing.T) {
|
|
||||||
expect(t, "{ key1: 1, key2: \"2\", key3: true }", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
exprStmt(
|
|
||||||
mapLit(p(1, 1), p(1, 34),
|
|
||||||
mapElementLit("key1", p(1, 3), p(1, 7), intLit(1, p(1, 9))),
|
|
||||||
mapElementLit("key2", p(1, 12), p(1, 16), stringLit("2", p(1, 18))),
|
|
||||||
mapElementLit("key3", p(1, 23), p(1, 27), boolLit(true, p(1, 29))))))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, "{ \"key1\": 1 }", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
exprStmt(
|
|
||||||
mapLit(p(1, 1), p(1, 13),
|
|
||||||
mapElementLit("key1", p(1, 3), p(1, 9), intLit(1, p(1, 11))))))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, "a = { key1: 1, key2: \"2\", key3: true }", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
assignStmt(
|
|
||||||
exprs(ident("a", p(1, 1))),
|
|
||||||
exprs(mapLit(p(1, 5), p(1, 38),
|
|
||||||
mapElementLit("key1", p(1, 7), p(1, 11), intLit(1, p(1, 13))),
|
|
||||||
mapElementLit("key2", p(1, 16), p(1, 20), stringLit("2", p(1, 22))),
|
|
||||||
mapElementLit("key3", p(1, 27), p(1, 31), boolLit(true, p(1, 33))))),
|
|
||||||
token.Assign,
|
|
||||||
p(1, 3)))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, "a = { key1: 1, key2: \"2\", key3: { k1: `bar`, k2: 4 } }", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
assignStmt(
|
|
||||||
exprs(ident("a", p(1, 1))),
|
|
||||||
exprs(mapLit(p(1, 5), p(1, 54),
|
|
||||||
mapElementLit("key1", p(1, 7), p(1, 11), intLit(1, p(1, 13))),
|
|
||||||
mapElementLit("key2", p(1, 16), p(1, 20), stringLit("2", p(1, 22))),
|
|
||||||
mapElementLit("key3", p(1, 27), p(1, 31),
|
|
||||||
mapLit(p(1, 33), p(1, 52),
|
|
||||||
mapElementLit("k1", p(1, 35), p(1, 37), stringLit("bar", p(1, 39))),
|
|
||||||
mapElementLit("k2", p(1, 46), p(1, 48), intLit(4, p(1, 50))))))),
|
|
||||||
token.Assign,
|
|
||||||
p(1, 3)))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, `
|
|
||||||
{
|
|
||||||
key1: 1,
|
|
||||||
key2: "2",
|
|
||||||
key3: true
|
|
||||||
}`, func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
exprStmt(
|
|
||||||
mapLit(p(2, 1), p(6, 1),
|
|
||||||
mapElementLit("key1", p(3, 2), p(3, 6), intLit(1, p(3, 8))),
|
|
||||||
mapElementLit("key2", p(4, 2), p(4, 6), stringLit("2", p(4, 8))),
|
|
||||||
mapElementLit("key3", p(5, 2), p(5, 6), boolLit(true, p(5, 8))))))
|
|
||||||
})
|
|
||||||
|
|
||||||
expectError(t, `
|
|
||||||
{
|
|
||||||
key1: 1,
|
|
||||||
key2: "2",
|
|
||||||
key3: true,
|
|
||||||
}`) // unlike Go, trailing comma for the last element is illegal
|
|
||||||
|
|
||||||
expectError(t, `{ key1: 1, }`)
|
|
||||||
expectError(t, `{
|
|
||||||
key1: 1,
|
|
||||||
key2: 2,
|
|
||||||
}`)
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
package parser_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestPrecedence(t *testing.T) {
|
|
||||||
expectString(t, `a + b + c`, `((a + b) + c)`)
|
|
||||||
expectString(t, `a + b * c`, `(a + (b * c))`)
|
|
||||||
expectString(t, `x = 2 * 1 + 3 / 4`, `x = ((2 * 1) + (3 / 4))`)
|
|
||||||
}
|
|
|
@ -1,165 +0,0 @@
|
||||||
package parser_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/d5/tengo/compiler/ast"
|
|
||||||
"github.com/d5/tengo/compiler/token"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSelector(t *testing.T) {
|
|
||||||
expect(t, "a.b", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
exprStmt(
|
|
||||||
selectorExpr(
|
|
||||||
ident("a", p(1, 1)),
|
|
||||||
stringLit("b", p(1, 3)))))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, "a.b.c", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
exprStmt(
|
|
||||||
selectorExpr(
|
|
||||||
selectorExpr(
|
|
||||||
ident("a", p(1, 1)),
|
|
||||||
stringLit("b", p(1, 3))),
|
|
||||||
stringLit("c", p(1, 5)))))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, "{k1:1}.k1", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
exprStmt(
|
|
||||||
selectorExpr(
|
|
||||||
mapLit(
|
|
||||||
p(1, 1), p(1, 6),
|
|
||||||
mapElementLit("k1", p(1, 2), p(1, 4), intLit(1, p(1, 5)))),
|
|
||||||
stringLit("k1", p(1, 8)))))
|
|
||||||
|
|
||||||
})
|
|
||||||
expect(t, "{k1:{v1:1}}.k1.v1", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
exprStmt(
|
|
||||||
selectorExpr(
|
|
||||||
selectorExpr(
|
|
||||||
mapLit(
|
|
||||||
p(1, 1), p(1, 11),
|
|
||||||
mapElementLit("k1", p(1, 2), p(1, 4),
|
|
||||||
mapLit(p(1, 5), p(1, 10),
|
|
||||||
mapElementLit("v1", p(1, 6), p(1, 8), intLit(1, p(1, 9)))))),
|
|
||||||
stringLit("k1", p(1, 13))),
|
|
||||||
stringLit("v1", p(1, 16)))))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, "a.b = 4", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
assignStmt(
|
|
||||||
exprs(selectorExpr(ident("a", p(1, 1)), stringLit("b", p(1, 3)))),
|
|
||||||
exprs(intLit(4, p(1, 7))),
|
|
||||||
token.Assign, p(1, 5)))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, "a.b.c = 4", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
assignStmt(
|
|
||||||
exprs(selectorExpr(selectorExpr(ident("a", p(1, 1)), stringLit("b", p(1, 3))), stringLit("c", p(1, 5)))),
|
|
||||||
exprs(intLit(4, p(1, 9))),
|
|
||||||
token.Assign, p(1, 7)))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, "a.b.c = 4 + 5", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
assignStmt(
|
|
||||||
exprs(selectorExpr(selectorExpr(ident("a", p(1, 1)), stringLit("b", p(1, 3))), stringLit("c", p(1, 5)))),
|
|
||||||
exprs(binaryExpr(intLit(4, p(1, 9)), intLit(5, p(1, 13)), token.Add, p(1, 11))),
|
|
||||||
token.Assign, p(1, 7)))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, "a[0].c = 4", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
assignStmt(
|
|
||||||
exprs(
|
|
||||||
selectorExpr(
|
|
||||||
indexExpr(
|
|
||||||
ident("a", p(1, 1)),
|
|
||||||
intLit(0, p(1, 3)),
|
|
||||||
p(1, 2), p(1, 4)),
|
|
||||||
stringLit("c", p(1, 6)))),
|
|
||||||
exprs(intLit(4, p(1, 10))),
|
|
||||||
token.Assign, p(1, 8)))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, "a.b[0].c = 4", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
assignStmt(
|
|
||||||
exprs(
|
|
||||||
selectorExpr(
|
|
||||||
indexExpr(
|
|
||||||
selectorExpr(
|
|
||||||
ident("a", p(1, 1)),
|
|
||||||
stringLit("b", p(1, 3))),
|
|
||||||
intLit(0, p(1, 5)),
|
|
||||||
p(1, 4), p(1, 6)),
|
|
||||||
stringLit("c", p(1, 8)))),
|
|
||||||
exprs(intLit(4, p(1, 12))),
|
|
||||||
token.Assign, p(1, 10)))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, "a.b[0][2].c = 4", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
assignStmt(
|
|
||||||
exprs(
|
|
||||||
selectorExpr(
|
|
||||||
indexExpr(
|
|
||||||
indexExpr(
|
|
||||||
selectorExpr(
|
|
||||||
ident("a", p(1, 1)),
|
|
||||||
stringLit("b", p(1, 3))),
|
|
||||||
intLit(0, p(1, 5)),
|
|
||||||
p(1, 4), p(1, 6)),
|
|
||||||
intLit(2, p(1, 8)),
|
|
||||||
p(1, 7), p(1, 9)),
|
|
||||||
stringLit("c", p(1, 11)))),
|
|
||||||
exprs(intLit(4, p(1, 15))),
|
|
||||||
token.Assign, p(1, 13)))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, `a.b["key1"][2].c = 4`, func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
assignStmt(
|
|
||||||
exprs(
|
|
||||||
selectorExpr(
|
|
||||||
indexExpr(
|
|
||||||
indexExpr(
|
|
||||||
selectorExpr(
|
|
||||||
ident("a", p(1, 1)),
|
|
||||||
stringLit("b", p(1, 3))),
|
|
||||||
stringLit("key1", p(1, 5)),
|
|
||||||
p(1, 4), p(1, 11)),
|
|
||||||
intLit(2, p(1, 13)),
|
|
||||||
p(1, 12), p(1, 14)),
|
|
||||||
stringLit("c", p(1, 16)))),
|
|
||||||
exprs(intLit(4, p(1, 20))),
|
|
||||||
token.Assign, p(1, 18)))
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(t, "a[0].b[2].c = 4", func(p pfn) []ast.Stmt {
|
|
||||||
return stmts(
|
|
||||||
assignStmt(
|
|
||||||
exprs(
|
|
||||||
selectorExpr(
|
|
||||||
indexExpr(
|
|
||||||
selectorExpr(
|
|
||||||
indexExpr(
|
|
||||||
ident("a", p(1, 1)),
|
|
||||||
intLit(0, p(1, 3)),
|
|
||||||
p(1, 2), p(1, 4)),
|
|
||||||
stringLit("b", p(1, 6))),
|
|
||||||
intLit(2, p(1, 8)),
|
|
||||||
p(1, 7), p(1, 9)),
|
|
||||||
stringLit("c", p(1, 11)))),
|
|
||||||
exprs(intLit(4, p(1, 15))),
|
|
||||||
token.Assign, p(1, 13)))
|
|
||||||
})
|
|
||||||
|
|
||||||
expectError(t, `a.(b.c)`)
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue