269 lines
5.9 KiB
Markdown
269 lines
5.9 KiB
Markdown
|
# The Tengo Language
|
|||
|
|
|||
|
Tengo is an embedded script language for Go.
|
|||
|
|
|||
|
\>> **Try Tengo in online [Playground](https://tengolang.com/)** <<
|
|||
|
|
|||
|
## Language Features
|
|||
|
|
|||
|
- Simple and intuitive syntax
|
|||
|
- Dynamically typed with type coercions
|
|||
|
- Bytecode compiled _(see the [benchmark](#benchmark) results)_
|
|||
|
- First-class functions and Closures
|
|||
|
- Garbage collected _(thanks to Go runtime)_
|
|||
|
- Easily extendible using customizable types
|
|||
|
- Written in pure Go _(no CGO, no external dependencies)_
|
|||
|
- Excutable as a standalone language _(without writing any Go code)_
|
|||
|
|
|||
|
## Benchmark
|
|||
|
|
|||
|
| | fib(35) | fibt(35) | Type |
|
|||
|
| :--- | ---: | ---: | :---: |
|
|||
|
| Go | `68,713,331` | `3,264,992` | Go (native) |
|
|||
|
| [**Tengo**](https://github.com/d5/tengo) | `6,811,234,411` | `4,699,512` | Go-VM |
|
|||
|
| Lua | `1,946,451,017` | `3,220,991` | Lua (native) |
|
|||
|
| [go-lua](https://github.com/Shopify/go-lua) | `5,658,423,479` | `4,247,160` | Go-Lua-VM |
|
|||
|
| [GopherLua](https://github.com/yuin/gopher-lua) | `6,301,424,553` | `5,194,735` | Go-Lua-VM |
|
|||
|
| Python | `3,159,870,102` | `28,512,040` | Python (native) |
|
|||
|
| [otto](https://github.com/robertkrimen/otto) | `91,616,109,035` | `13,780,650` | Go-JS-Interpreter |
|
|||
|
| [Anko](https://github.com/mattn/anko) | `119,395,411,432` | `22,266,008` | Go-Interpreter |
|
|||
|
|
|||
|
_*Nanoseconds_
|
|||
|
|
|||
|
`fib(35)` is a function to calculate 35th Fibonacci number.
|
|||
|
|
|||
|
```golang
|
|||
|
fib := func(x) {
|
|||
|
if x == 0 {
|
|||
|
return 0
|
|||
|
} else if x == 1 {
|
|||
|
return 1
|
|||
|
} else {
|
|||
|
return fib(x-1) + fib(x-2)
|
|||
|
}
|
|||
|
}
|
|||
|
fib(35)
|
|||
|
```
|
|||
|
|
|||
|
`fibt(35)` is a [tail-call](https://en.wikipedia.org/wiki/Tail_call) version of `fib(35)`.
|
|||
|
|
|||
|
```golang
|
|||
|
fibt := func(x, a, b) {
|
|||
|
if x == 0 {
|
|||
|
return a
|
|||
|
} else if x == 1 {
|
|||
|
return b
|
|||
|
} else {
|
|||
|
return fibt(x-1, b, a+b)
|
|||
|
}
|
|||
|
}
|
|||
|
fibt(35, 0, 1)
|
|||
|
```
|
|||
|
|
|||
|
Please see [tengobench](https://github.com/d5/tengobench) for more details.
|
|||
|
|
|||
|
## Tengo Syntax in 5 Minutes
|
|||
|
|
|||
|
Tengo supports line comments (`//...`) and block comments (`/* ... */`).
|
|||
|
|
|||
|
```golang
|
|||
|
/*
|
|||
|
multi-line block comments
|
|||
|
*/
|
|||
|
|
|||
|
a := 5 // line comments
|
|||
|
```
|
|||
|
|
|||
|
Tengo is a dynamically typed language, and, you can initialize the variables using `:=` operator.
|
|||
|
|
|||
|
```golang
|
|||
|
a := 1984 // int
|
|||
|
b := "aomame" // string
|
|||
|
c := -9.22 // float
|
|||
|
d := true // bool
|
|||
|
e := '九' // char
|
|||
|
f := [1, false, "foo"] // array
|
|||
|
g := { // map
|
|||
|
h: 439,
|
|||
|
i: 12.34,
|
|||
|
j: [0, 9, false]
|
|||
|
}
|
|||
|
k := func(l, m) { // function
|
|||
|
return l + m
|
|||
|
}
|
|||
|
```
|
|||
|
After the variable is initialized, it can be re-assigned different value using `=` operator.
|
|||
|
|
|||
|
```golang
|
|||
|
a := 1928 // int
|
|||
|
a = "foo" // string
|
|||
|
f := func() {
|
|||
|
a := false // 'a' is defined in the function scope
|
|||
|
a = [1, 2, 3] // and thus does not affect 'a' in global scope.
|
|||
|
}
|
|||
|
print(a) // still "foo"
|
|||
|
```
|
|||
|
|
|||
|
Type is not explicitly specified, but, you can use type coercion functions to convert between types.
|
|||
|
|
|||
|
```golang
|
|||
|
s1 := string(1984) // "1984"
|
|||
|
i2 := int("-999") // -999
|
|||
|
f3 := float(-51) // -51.0
|
|||
|
b4 := bool(1) // true
|
|||
|
c5 := char("X") // 'X'
|
|||
|
```
|
|||
|
|
|||
|
You can use dot selector (`.`) and indexer (`[]`) operator to read or write elemens of arrays or maps.
|
|||
|
|
|||
|
```golang
|
|||
|
["one", "two", "three"][1] // == "two"
|
|||
|
|
|||
|
m := {
|
|||
|
a: 1,
|
|||
|
b: [2, 3, 4],
|
|||
|
c: func() { return 10 }
|
|||
|
}
|
|||
|
m.a // == 1
|
|||
|
m["b"][1] // == 3
|
|||
|
m.c() // == 10
|
|||
|
m.x = 5 // add 'x' to map 'm'
|
|||
|
m.b[5] = 0 // but this is an error: index out of bounds
|
|||
|
```
|
|||
|
|
|||
|
For sequence types (string or array), you can use slice operator (`[:]`) too.
|
|||
|
|
|||
|
```golang
|
|||
|
[1, 2, 3, 4, 5][1:3] // == [2, 3]
|
|||
|
[1, 2, 3, 4, 5][3:] // == [4, 5]
|
|||
|
[1, 2, 3, 4, 5][:3] // == [1, 2, 3]
|
|||
|
"hello world"[2:10] // == "llo worl"
|
|||
|
```
|
|||
|
|
|||
|
In Tengo, functions are first-class citizen and be treated like any other variables. Tengo also supports closures, functions that captures variables in outer scopes. In the following example, the function that's being returned from `adder` function is capturing `base` variable.
|
|||
|
|
|||
|
```golang
|
|||
|
adder := func(base) {
|
|||
|
return func(x) { return base + x } // capturing 'base'
|
|||
|
}
|
|||
|
add5 := adder(5)
|
|||
|
nine := add5(4) // nine
|
|||
|
```
|
|||
|
|
|||
|
For flow control, Tengo currently supports **if-else**, **for**, **for-in** statements.
|
|||
|
|
|||
|
```golang
|
|||
|
// IF-ELSE
|
|||
|
if a < 0 {
|
|||
|
// ...
|
|||
|
} else if a == 0 {
|
|||
|
// ...
|
|||
|
} else {
|
|||
|
// ...
|
|||
|
}
|
|||
|
|
|||
|
// IF with init statement
|
|||
|
if a := 0; a < 10 {
|
|||
|
// ...
|
|||
|
} else {
|
|||
|
// ...
|
|||
|
}
|
|||
|
|
|||
|
// FOR
|
|||
|
for a:=0; a<10; a++ {
|
|||
|
// ...
|
|||
|
}
|
|||
|
|
|||
|
// FOR condition-only (like WHILE in other languages)
|
|||
|
for a < 10 {
|
|||
|
// ...
|
|||
|
}
|
|||
|
|
|||
|
// FOR-IN
|
|||
|
for x in [1, 2, 3] { // array: element
|
|||
|
// ...
|
|||
|
}
|
|||
|
for i, x in [1, 2, 3] { // array: index and element
|
|||
|
// ...
|
|||
|
}
|
|||
|
for k, v in {k1: 1, k2: 2} { // map: key and value
|
|||
|
// ...
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
## Embed Tengo in Go
|
|||
|
|
|||
|
...
|
|||
|
|
|||
|
```golang
|
|||
|
import "github.com/d5/tengo/script"
|
|||
|
|
|||
|
var code = `
|
|||
|
reduce := func(seq, fn) {
|
|||
|
s := 0
|
|||
|
for x in seq { fn(x, s) }
|
|||
|
return s
|
|||
|
}
|
|||
|
|
|||
|
print(reduce([1, 2, 3], func(x, s) { s += x }))
|
|||
|
`
|
|||
|
|
|||
|
func main() {
|
|||
|
s := script.New([]byte(code))
|
|||
|
if _, err := s.Run(); err != nil {
|
|||
|
panic(err)
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
...
|
|||
|
|
|||
|
|
|||
|
## Tengo as a Standalone Language
|
|||
|
|
|||
|
Although Tengo is designed as an embedded script language for Go, it can be compiled and executed as native binary without any Go code using `tengo` tool.
|
|||
|
|
|||
|
### Installing Tengo Tool
|
|||
|
|
|||
|
To install `tengo` tool, run:
|
|||
|
|
|||
|
```bash
|
|||
|
go get github.com/d5/tengo/cmd/tengo
|
|||
|
```
|
|||
|
|
|||
|
### Compiling and Executing Tengo Code
|
|||
|
|
|||
|
You can directly execute the Tengo source code by running `tengo` tool with your Tengo source file (`*.tengo`).
|
|||
|
|
|||
|
```bash
|
|||
|
tengo myapp.tengo
|
|||
|
```
|
|||
|
|
|||
|
Or, you can compile the code into a binary file and execute it later.
|
|||
|
|
|||
|
```bash
|
|||
|
tengo -c -o myapp myapp.tengo # compile 'myapp.tengo' into binary file 'myapp'
|
|||
|
tengo myapp # execute the compiled binary `myapp`
|
|||
|
```
|
|||
|
|
|||
|
### Tengo REPL
|
|||
|
|
|||
|
You can run Tengo [REPL](https://en.wikipedia.org/wiki/Read–eval–print_loop) if you run `tengo` with no arguments.
|
|||
|
|
|||
|
```bash
|
|||
|
tengo
|
|||
|
```
|
|||
|
|
|||
|
## Roadmap
|
|||
|
|
|||
|
The next big features planned include:
|
|||
|
|
|||
|
- Module system _(or packages)_
|
|||
|
- Standard libraries
|
|||
|
- Better documentations
|
|||
|
- More language constructs such as error handling, object methods, switch-case statements
|
|||
|
- Native executables compilation
|
|||
|
- Performance improvements
|
|||
|
- Syntax highlighter for IDEs
|
|||
|
|