some code clean up (#237)

This commit is contained in:
daniel 2019-12-20 11:40:38 -08:00 committed by GitHub
parent 030b4b90ab
commit c88a5f506e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
297 changed files with 19913 additions and 19430 deletions

47
.circleci/config.yml Normal file
View 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]+)*(-.*)*/

View file

@ -11,15 +11,6 @@ builds:
- darwin
- linux
- windows
- env:
- CGO_ENABLED=0
main: ./cmd/tengomin/main.go
id: tengomin
binary: tengomin
goos:
- darwin
- linux
- windows
archive:
files:
- none*

View file

@ -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

View file

@ -1,13 +1,10 @@
vet:
go vet ./...
generate:
go generate ./...
lint:
golint -set_exit_status ./...
test: generate vet lint
test: generate lint
go test -race -cover ./...
fmt:

View file

@ -4,18 +4,18 @@
# 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)
[![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)
**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
/* The Tengo Language */
fmt := import("fmt")
each := func(seq, fn) {
@ -31,19 +31,24 @@ fmt.println(sum(0, [1, 2, 3])) // "6"
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
- 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
- Higher-order functions and closures
- Immutable values
- Garbage collection
- [Securely Embeddable](https://github.com/d5/tengo/blob/master/docs/interoperability.md) and [Extensible](https://github.com/d5/tengo/blob/master/docs/objects.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)
- 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
- 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)
- Executable as a
[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
@ -61,16 +66,70 @@ fmt.println(sum("", [1, 2, 3])) // "123"
| [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 |
_* [fib(35)](https://github.com/d5/tengobench/blob/master/code/fib.tengo): 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)_
_* [fib(35)](https://github.com/d5/tengobench/blob/master/code/fib.tengo):
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_
_* 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
- [Language Syntax](https://github.com/d5/tengo/blob/master/docs/tutorial.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)
- [Interoperability](https://github.com/d5/tengo/blob/master/docs/interoperability.md)
- [Tengo CLI](https://github.com/d5/tengo/blob/master/docs/tengo-cli.md)

View file

@ -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
}

View file

@ -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
View 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
View 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
View 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)
}

View file

@ -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
}

View file

@ -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))
}

View file

@ -4,12 +4,8 @@ import (
"fmt"
"time"
"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"
"github.com/d5/tengo"
"github.com/d5/tengo/internal"
)
func main() {
@ -40,8 +36,9 @@ fib := func(x) {
panic(err)
}
if nativeResult != int(result.(*objects.Int).Value) {
panic(fmt.Errorf("wrong result: %d != %d", nativeResult, int(result.(*objects.Int).Value)))
if nativeResult != int(result.(*tengo.Int).Value) {
panic(fmt.Errorf("wrong result: %d != %d", nativeResult,
int(result.(*tengo.Int).Value)))
}
fmt.Println("-------------------------------------")
@ -76,8 +73,9 @@ fib := func(x, s) {
panic(err)
}
if nativeResult != int(result.(*objects.Int).Value) {
panic(fmt.Errorf("wrong result: %d != %d", nativeResult, int(result.(*objects.Int).Value)))
if nativeResult != int(result.(*tengo.Int).Value) {
panic(fmt.Errorf("wrong result: %d != %d", nativeResult,
int(result.(*tengo.Int).Value)))
}
fmt.Println("-------------------------------------")
@ -112,8 +110,9 @@ fib := func(x, a, b) {
panic(err)
}
if nativeResult != int(result.(*objects.Int).Value) {
panic(fmt.Errorf("wrong result: %d != %d", nativeResult, int(result.(*objects.Int).Value)))
if nativeResult != int(result.(*tengo.Int).Value) {
panic(fmt.Errorf("wrong result: %d != %d", nativeResult,
int(result.(*tengo.Int).Value)))
}
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) {
var astFile *ast.File
func runBench(
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)
if err != nil {
return
}
var bytecode *compiler.Bytecode
var bytecode *tengo.Bytecode
compileTime, bytecode, err = compileFile(astFile)
if err != nil {
return
@ -173,13 +180,13 @@ func runBench(input []byte) (parseTime time.Duration, compileTime time.Duration,
return
}
func parse(input []byte) (time.Duration, *ast.File, error) {
fileSet := source.NewFileSet()
func parse(input []byte) (time.Duration, *internal.File, error) {
fileSet := internal.NewFileSet()
inputFile := fileSet.AddFile("bench", -1, len(input))
start := time.Now()
p := parser.NewParser(inputFile, input, nil)
p := internal.NewParser(inputFile, input, nil)
file, err := p.ParseFile()
if err != nil {
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
}
func compileFile(file *ast.File) (time.Duration, *compiler.Bytecode, error) {
symTable := compiler.NewSymbolTable()
func compileFile(file *internal.File) (time.Duration, *tengo.Bytecode, error) {
symTable := internal.NewSymbolTable()
symTable.Define("out")
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 {
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
}
func runVM(bytecode *compiler.Bytecode) (time.Duration, objects.Object, error) {
globals := make([]objects.Object, runtime.GlobalsSize)
func runVM(
bytecode *tengo.Bytecode,
) (time.Duration, tengo.Object, error) {
globals := make([]tengo.Object, tengo.GlobalsSize)
start := time.Now()
v := runtime.NewVM(bytecode, globals, -1)
v := tengo.NewVM(bytecode, globals, -1)
if err := v.Run(); err != nil {
return time.Since(start), nil, err
}

View file

@ -1,12 +1,26 @@
package main
import (
"bufio"
"bytes"
"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"
)
const (
sourceFileExt = ".tengo"
replPrompt = ">> "
)
var (
compileOutput string
showHelp bool
@ -22,12 +36,274 @@ func init() {
}
func main() {
cli.Run(&cli.Options{
ShowHelp: showHelp,
ShowVersion: showVersion,
Version: version,
CompileOutput: compileOutput,
Modules: stdlib.GetModuleMap(stdlib.AllModuleNames()...),
InputFile: flag.Arg(0),
})
if showHelp {
doHelp()
os.Exit(2)
} else if showVersion {
fmt.Println(version)
return
}
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
}

View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -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, ", ") + "]"
}

View file

@ -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, ", ")
}

View file

@ -1,5 +0,0 @@
package ast
const (
nullRep = "<null>"
)

View file

@ -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>"
}

View file

@ -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>"
}

View file

@ -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() + ")"
}

View file

@ -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, "; ") + "}"
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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, ", ") + ")"
}

View file

@ -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
}

View file

@ -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() + ")"
}

View file

@ -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 ";"
}

View file

@ -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() + ")"
}

View file

@ -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()
}

View file

@ -1,7 +0,0 @@
package ast
// Expr represents an expression node in the AST.
type Expr interface {
Node
exprNode()
}

View file

@ -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()
}

View file

@ -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
}

View file

@ -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()
}

View file

@ -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()
}

View file

@ -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()
}

View file

@ -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()
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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() + ")"
}

View file

@ -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 + `")"`
}

View file

@ -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()
}

View file

@ -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 + "]"
}

View file

@ -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
}

View file

@ -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()
}

View file

@ -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, ", ") + "}"
}

View file

@ -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
}

View file

@ -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() + ")"
}

View file

@ -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"
}

View file

@ -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()
}

View file

@ -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 + "]"
}

View file

@ -1,7 +0,0 @@
package ast
// Stmt represents a statement in the AST.
type Stmt interface {
Node
stmtNode()
}

View file

@ -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
}

View file

@ -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() + ")"
}

View file

@ -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"
}

View file

@ -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{})
}

View file

@ -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
}

View file

@ -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 ""
}

View file

@ -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)
}

View file

@ -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
}

View file

@ -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("}")
}

View file

@ -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
}

View file

@ -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")
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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)))))
}

View file

@ -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
}

View file

@ -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

View file

@ -1,8 +0,0 @@
package compiler
// EmittedInstruction represents an opcode
// with its emitted position.
type EmittedInstruction struct {
Opcode Opcode
Position int
}

View file

@ -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)
}

View file

@ -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))
}

View file

@ -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
}

View file

@ -1,4 +0,0 @@
package compiler
// ModuleLoader should take a module name and return the module data.
type ModuleLoader func(moduleName string) ([]byte, error)

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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
}

View file

@ -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())
}

View file

@ -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())
}

View file

@ -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()
}

View file

@ -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, ,]`)
}

View file

@ -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)))
})
}

View file

@ -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))))
})
}

View file

@ -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))))
})
}

View file

@ -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九'`)
}

View file

@ -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`)
}

View file

@ -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
}

View file

@ -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)))
})
}

View file

@ -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)))
})
}

View file

@ -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 }")
}

View file

@ -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 {}`)
}

View file

@ -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)))
})
}

View file

@ -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))))
})
}

View file

@ -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))))
})
}

View file

@ -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,
}`)
}

View file

@ -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))`)
}

View file

@ -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