Merge pull request #36 from d5/doc

Documentation directory (/docs)
This commit is contained in:
Daniel Kang 2019-01-23 20:16:36 -08:00 committed by GitHub
commit c816b705c1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 1153 additions and 296 deletions

339
README.md
View file

@ -8,21 +8,38 @@
[![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)
Tengo is an embeddable script language for Go.
**Tengo is a small, dynamic, fast, secure script language for Go.**
Tengo is [fast](#benchmark) as it's compiled to bytecode and executed 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.
\>> **Try [Tengo Playground](https://tengolang.com/)** <<
```golang
/* The Tengo Language */
each := func(seq, fn) {
for x in seq { fn(x) }
}
sum := func(init, seq) {
each(seq, func(x) { init += x })
return init
}
n := sum(0, [1, 2, 3]) // == 6
s := sum("", [1, 2, 3]) // == "123"
```
> Run this code in the [Playground](https://tengolang.com/?s=d01cf9ed81daba939e26618530eb171f7397d9c9)
## Features
- Simple and intuitive [syntax](https://github.com/d5/tengo#tengo-syntax-in-5-minutes)
- Dynamically typed with type coercions
- First-class functions and Closures
- Garbage collected _(thanks to Go runtime)_
- Easily extensible using customizable types
- Written in pure Go _(no CGO, no external dependencies)_
- Executable as a standalone language
- 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 _(v1)_
- 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)
- 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
## Benchmark
@ -39,290 +56,20 @@ Tengo is [fast](#benchmark) as it's compiled to bytecode and executed on stack-b
| [otto](https://github.com/robertkrimen/otto) | `82,050ms` | `22ms` | JS Interpreter on Go |
| [Anko](https://github.com/mattn/anko) | `98,739ms` | `31ms` | Interpreter on Go |
[fib(35)](https://github.com/d5/tengobench/blob/master/code/fib.tengo) is a function to compute 35th Fibonacci number, and, [fibt(35)](https://github.com/d5/tengobench/blob/master/code/fibtc.tengo) is the [tail-call](https://en.wikipedia.org/wiki/Tail_call) version of the same function.
_Please note that **Go** case does not read the source code from a local file, while all other cases do. All shell commands and the source code used in this benchmarking is available [here](https://github.com/d5/tengobench)._
## Tengo Syntax in 5 Minutes
Tengo supports line comments (`//...`) and block comments (`/* ... */`).
```golang
/*
multi-line block comments
*/
a := 5 // line comments
```
> [Run in Playground](https://tengolang.com/?s=02e384399a0397b0a752f08604ccb244d1a6cb37)
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
}
```
> [Run in Playground](https://tengolang.com/?s=f8626a711769502ce20e4560ace65c0e9c1279f4)
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.
}
a == "foo" // still "foo"
```
> [Run in Playground](https://tengolang.com/?s=1d39bc2af5c51417df82b32db47a0e6a156d48ec)
Type is not directly 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'
```
> [Run in Playground](https://tengolang.com/?s=8d57905b82959eb244e9bbd2111e12ee04a33045)
_See [Variable Types](https://github.com/d5/tengo/wiki/Variable-Types) for more details on the variable types._
You can use the dot selector (`.`) and indexer (`[]`) operator to read or write elements of arrays, strings, 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
```
> [Run in Playground](https://tengolang.com/?s=d510c75ed8f06ef1e22c1aaf8a7d4565c793514c)
For sequence types (string, bytes, array), you can use slice operator (`[:]`) too.
```golang
a := [1, 2, 3, 4, 5][1:3] // == [2, 3]
b := [1, 2, 3, 4, 5][3:] // == [4, 5]
c := [1, 2, 3, 4, 5][:3] // == [1, 2, 3]
d := "hello world"[2:10] // == "llo worl"
```
> [Run in Playground](https://tengolang.com/?s=214ab490bb24549578770984985f6b161aed915d)
In Tengo, functions are first-class citizen, and, it also supports closures, functions that captures variables in outer scopes. In the following example, the function returned from `adder` is capturing `base` variable.
```golang
adder := func(base) {
return func(x) { return base + x } // capturing 'base'
}
add5 := adder(5)
nine := add5(4) // == 9
```
> [Run in Playground](https://tengolang.com/?s=fba79990473d5b38cc944dfa225d38580ddaf422)
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
// ...
}
```
An error object is created using `error` function-like keyword. An error can have any types of value and the underlying value of the error can be accessed using `.value` selector.
```golang
err1 := error("oops") // error with string value
err2 := error(1+2+3) // error with int value
if is_error(err1) { // 'is_error' builtin function
err_val := err1.value // get underlying value
}
```
> [Run in Playground](https://tengolang.com/?s=5eaba4289c9d284d97704dd09cb15f4f03ad05c1)
You can load other scripts as modules using `import` expression.
Main script:
```golang
mod1 := import("./mod1") // assuming mod1.tengo file exists in the current directory
// same as 'import("./mod1.tengo")' or 'import("mod1")'
mod1.func1(a) // module function
a += mod1.foo // module variable
//mod1.foo = 5 // error: module variables are read-only
```
`mod1.tengo` file:
```golang
func1 := func(x) { print(x) }
foo := 2
```
Basically, `import` expression returns all the global variables defined in the module as a Map-like value. One can access the functions or variables defined in the module using `.` selector or `["key"]` indexer, but, module variables are immutable.
Also, you can use `import` to load the [standard libraries](https://github.com/d5/tengo/wiki/Standard-Libraries).
```golang
math := import("math")
a := math.abs(-19.84) // == 19.84
```
## Embedding Tengo in Go
To execute Tengo code in your Go codebase, you should use **Script**. In the simple use cases, all you need is to do is to create a new Script instance and call its `Script.Run()` function.
```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)
}
}
```
If you want to compile the source script once and execute it multiple times, you can use `Script.Compile()` function that returns **Compiled** instance.
```golang
import (
"fmt"
"github.com/d5/tengo/script"
)
func main() {
s := script.New([]byte(`a := b + 20`))
// define variable 'b'
_ = s.Add("b", 10)
// compile the source
c, err := s.Compile()
if err != nil {
panic(err)
}
// run the compiled bytecode
// a compiled bytecode 'c' can be executed multiple without re-compiling it
if err := c.Run(); err != nil {
panic(err)
}
// retrieve value of 'a'
a := c.Get("a")
fmt.Println(a.Int())
}
```
In the example above, a variable `b` is defined by the user before compilation using `Script.Add()` function. Then a compiled bytecode `c` is used to execute the bytecode and get the value of global variables. In this example, the value of global variable `a` is read using `Compiled.Get()` function.
One can easily use the custom data types by implementing `objects.Object` interface. See [Interoperability](https://github.com/d5/tengo/wiki/Interoperability) for more details.
As an alternative to using **Script**, you can directly create and interact with the parser, compiler, and, VMs directly. There's no good documentation yet, but, check out Script code if you are interested.
## Tengo CLI Tool
Although Tengo is designed as an embedded script language for Go, it can be compiled and executed as native binary 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/Readevalprint_loop) if you run `tengo` with no arguments.
```bash
tengo
```
_* [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_
## References
- [Language Syntax](https://github.com/d5/tengo/blob/master/docs/tutorial.md)
- [Tengo Objects](https://github.com/d5/tengo/blob/master/docs/objects.md)
- [Runtime Types](https://github.com/d5/tengo/blob/master/docs/runtime-types.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)
- [Standard Library](https://github.com/d5/tengo/blob/master/docs/stdlib.md) _(experimental)_
## Roadmap
@ -330,13 +77,13 @@ tengo
Things are experimental, and, the focus is on the **core language features**, **stability**, **basic interoperability**, and the **performance optimization**.
### v1. Tengo as a Script Language
### [v1. Tengo as a Script Language](https://github.com/d5/tengo/labels/v1.0)
This will be the first _versioned_ release, and, the main goal for v1 is to make Tengo as a _fast_ embeddable script language for Go, which means Tengo will be comparable to other Go-based script languages such as [Starlark](https://github.com/google/starlark-go), [Lua](https://github.com/Shopify/go-lua) [VM](https://github.com/yuin/gopher-lua)s, and [other](https://github.com/robertkrimen/otto) [interpreter](https://github.com/mattn/anko)s.
- Interoperability with Go code
- Sandbox environment
- More language features such as bound methods and switch-case statements
- More language features
### v2. Tengo as a Standalone Language

162
docs/builtins.md Normal file
View file

@ -0,0 +1,162 @@
# Builtin Functions
## print
Prints a string representation of the given variable to the standard output.
```golang
v := [1, 2, 3]
print(v) // "[1, 2, 3]"
```
## len
Returns the number of elements if the given variable is array, string, map, or module map.
```golang
v := [1, 2, 3]
l := len(v) // l == 3
```
## copy
Creates a copy of the given variable. `copy` function calls `Object.Copy` interface method, which is expected to return a deep-copy of the value it holds.
```golang
v1 := [1, 2, 3]
v2 := v1
v3 := copy(v1)
v1[1] = 0
print(v2[1]) // "0"; 'v1' and 'v2' referencing the same array
print(v3[1]) // "2"; 'v3' not affected by 'v1'
```
## append
Appends object(s) to an array (first argument) and returns a new array object. (Like Go's `append` builtin.) Currently, this function takes array type only.
```golang
v := [1]
v = append(v, 2, 3) // v == [1, 2, 3]
```
## string
Tries to convert an object to string object. See [this](https://github.com/d5/tengo/wiki/Variable-Types) for more details on type conversion.
```golang
x := string(123) // v == "123"
```
Optionally it can take the second argument, which will be returned if the first argument cannot be converted to string. Note that the second argument does not have to be string.
```golang
v = string(undefined, "foo") // v == "foo"
v = string(undefined, false) // v == false
```
## int
Tries to convert an object to int object. See [this](https://github.com/d5/tengo/blob/master/docs/runtime-types.md) for more details on type conversion.
```golang
v := int("123") // v == 123
```
Optionally it can take the second argument, which will be returned if the first argument cannot be converted to int. Note that the second argument does not have to be int.
```golang
v = int(undefined, 10) // v == 10
v = int(undefined, false) // v == false
```
## bool
Tries to convert an object to bool object. See [this](https://github.com/d5/tengo/blob/master/docs/runtime-types.md) for more details on type conversion.
```golang
v := bool(1) // v == true
```
## float
Tries to convert an object to float object. See [this](https://github.com/d5/tengo/blob/master/docs/runtime-types.md) for more details on type conversion.
```golang
v := float("19.84") // v == 19.84
```
Optionally it can take the second argument, which will be returned if the first argument cannot be converted to float. Note that the second argument does not have to be float.
```golang
v = float(undefined, 19.84) // v == 19.84
v = float(undefined, false) // v == false
```
## char
Tries to convert an object to char object. See [this](https://github.com/d5/tengo/blob/master/docs/runtime-types.md) for more details on type conversion.
```golang
v := char(89) // v == 'Y'
```
Optionally it can take the second argument, which will be returned if the first argument cannot be converted to float. Note that the second argument does not have to be float.
```golang
v = char(undefined, 'X') // v == 'X'
v = char(undefined, false) // v == false
```
## bytes
Tries to convert an object to bytes object. See [this](https://github.com/d5/tengo/blob/master/docs/runtime-types.md) for more details on type conversion.
```golang
v := bytes("foo") // v == [102 111 111]
```
Optionally it can take the second argument, which will be returned if the first argument cannot be converted to float. Note that the second argument does not have to be float.
```golang
v = bytes(undefined, bytes("foo")) // v == bytes("foo")
v = bytes(undefined, false) // v == false
```
If you pass an int to `bytes()` function, it will create a new byte object with the given size.
```golang
v := bytes(100)
```
## is_string
Returns `true` if the object is string. Or it returns `false`.
## is_int
Returns `true` if the object is int. Or it returns `false`.
## is_bool
Returns `true` if the object is bool. Or it returns `false`.
## is_float
Returns `true` if the object is float. Or it returns `false`.
## is_char
Returns `true` if the object is char. Or it returns `false`.
## is_bytes
Returns `true` if the object is bytes. Or it returns `false`.
## is_error
Returns `true` if the object is error. Or it returns `false`.
## is_undefined
Returns `true` if the object is undefined. Or it returns `false`.

116
docs/interoperability.md Normal file
View file

@ -0,0 +1,116 @@
# Interoperability
## Table of Contents
- [Using Scripts](#using-scripts)
- [Type Conversion Table](#type-conversion-table)
- [Compiler and VM](#compiler-and-vm)
- [User Types](#user-types)
- [Sandbox Environments](#sandbox-environments)
## Using Scripts
Embedding and executing the Tengo code in Go is very easy. At a high level, this process is like:
- create a [Script](https://godoc.org/github.com/d5/tengo/script#Script) instance with your code,
- _optionally_ add some [Script Variables](https://godoc.org/github.com/d5/tengo/script#Variable) to Script,
- compile or directly run the script,
- retrieve _output_ values from the [Compiled](https://godoc.org/github.com/d5/tengo/script#Compiled) instance.
The following is an example where a Tengo script is compiled and run with no input/output variables.
```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)
}
}
```
Here's another example where an input variable is added to the script, and, an output variable is accessed through [Variable.Int](https://godoc.org/github.com/d5/tengo/script#Variable.Int) function:
```golang
import (
"fmt"
"github.com/d5/tengo/script"
)
func main() {
s := script.New([]byte(`a := b + 20`))
// define variable 'b'
_ = s.Add("b", 10)
// compile the source
c, err := s.Compile()
if err != nil {
panic(err)
}
// run the compiled bytecode
// a compiled bytecode 'c' can be executed multiple without re-compiling it
if err := c.Run(); err != nil {
panic(err)
}
// retrieve value of 'a'
a := c.Get("a")
fmt.Println(a.Int())
}
```
A variable `b` is defined by the user before compilation using [Script.Add](https://godoc.org/github.com/d5/tengo/script#Script.Add) function. Then a compiled bytecode `c` is used to execute the bytecode and get the value of global variables. In this example, the value of global variable `a` is read using [Compiled.Get](https://godoc.org/github.com/d5/tengo/script#Compiled.Get) function. See [documentation](https://godoc.org/github.com/d5/tengo/script#Variable) for the full list of variable value functions.
### Type Conversion Table
When adding a Variable _([Script.Add](https://godoc.org/github.com/d5/tengo/script#Script.Add))_, Script converts Go values into Tengo values based on the following conversion table.
| Go Type | Tengo Type | Note |
| :--- | :--- | :--- |
|`nil`|`Undefined`||
|`string`|`String`||
|`int64`|`Int`||
|`int`|`Int`||
|`bool`|`Bool`||
|`rune`|`Char`||
|`byte`|`Char`||
|`float64`|`Float`||
|`[]byte`|`Bytes`||
|`error`|`Error{String}`|use `error.Error()` as String value|
|`map[string]Object`|`Map`||
|`map[string]interface{}`|`Map`|individual elements converted to Tengo objects|
|`[]Object`|`Array`||
|`[]interface{}`|`Array`|individual elements converted to Tengo objects|
|`Object`|`Object`|_(no type conversion performed)_|
## User Types
One can easily add and use customized value types in Tengo code by implementing [Object](https://godoc.org/github.com/d5/tengo/objects#Object) interface. Tengo runtime will treat the user types exactly in the same way it does to the runtime types with no performance overhead. See [Tengo Objects](https://github.com/d5/tengo/blob/master/docs/objects.md) for more details.
## Compiler and VM
Although it's not recommended, you can directly create and run the Tengo [Parser](https://godoc.org/github.com/d5/tengo/compiler/parser#Parser), [Compiler](https://godoc.org/github.com/d5/tengo/compiler#Compiler), and [VM](https://godoc.org/github.com/d5/tengo/runtime#VM) for yourself instead of using Scripts and Script Variables. It's a bit more involved as you have to manage the symbol tables and global variables between them, but, basically that's what Script and Script Variable is doing internally.
_TODO: add more information here_
## Sandbox Environments
In an environment where a _(potentially)_ unsafe script code needs to be executed,
_TODO: add more information here_

297
docs/objects.md Normal file
View file

@ -0,0 +1,297 @@
# Tengo Objects
## Table of Contents
- [Objects](#objects)
- [Runtime Object Types](#runtime-object-types)
- [User Object Types](#user-object-types)
- [Callable Objects](#callable-objects)
- [Indexable Objects](#indexable-objects)
- [Index-Assignable Objects](#index-assignable-objects)
- [Iterable Objects](#iterable-objects)
- [Iterator Interface](#iterator-interface)
## Objects
All object types in Tengo implement [Object](https://godoc.org/github.com/d5/tengo/objects#Object) interface.
```golang
TypeName() string
```
TypeName method should return the name of the type. Type names are not directly used by the runtime _(except when it reports a run-time error)_, but, it is generally a good idea to keep it short but distinguishable from other types.
```golang
String() string
```
String method should return a string representation of the underlying value. The value returned by String method will be used whenever string formatting for the value is required, most commonly when being converted into String value.
```golang
BinaryOp(op token.Token, rhs Object) (res Object, err error)
```
In Tengo, a type can overload binary operators (`+`, `-`, `*`, `/`, `%`, `&`, `|`, `^`, `&^`, `>>`, `<<`, `>`, `>=`; _note that `<` and `<=` operators are not overloadable as they're simply implemented by switching left-hand side and right-hand side of `>`/`>=` operator_) by implementing BinaryOp method. BinaryOp method takes the operator `op` and the right-hand side object `rhs`, and, should return a resulting value `res`.
**Error value vs runtime error**
If BinaryOp method returns an error `err` (the second return value), it will be treated as a run-time error, which will halt the execution (`VM.Run() error`) and will return the error to the user. All runtime type implementations, for example, will return an `ErrInvalidOperator` error when the given operator is not supported by the type.
Alternatively the method can return an `Error` value as its result `res` (the first return value), which will not halt the runtime and will be treated like any other values. As a dynamically typed language, the receiver (another expression or statement) can determine how to translate `Error` value returned from binary operator expression.
```golang
IsFalsy() bool
```
IsFalsy method should return true if the underlying value is considered to be [falsy](https://github.com/d5/tengo/wiki/Variable-Types#objectisfalsy).
```golang
Equals(o Object) bool
```
Equals method should return true if the underlying value is considered to be equal to the underlying value of another object `o`. When comparing values of different types, the runtime does not guarantee or force anything, but, it's generally a good idea to make the result consistent. For example, a custom integer type may return true when comparing against String value, but, it should return the same result for the same inputs.
```golang
Copy() Object
```
Copy method should a _new_ copy of the same object. All primitive and composite value types implement this method to return a deep-copy of the value, which is recommended for other user types _(as `copy` builtin function uses this Copy method)_, but, it's not a strict requirement by the runtime.
### Runtime Object Types
These are the Tengo runtime object types:
- Primitive value types: [Int](https://godoc.org/github.com/d5/tengo/objects#Int), [String](https://godoc.org/github.com/d5/tengo/objects#String), [Float](https://godoc.org/github.com/d5/tengo/objects#Float), [Bool](https://godoc.org/github.com/d5/tengo/objects#ArrayIterator), [Char](https://godoc.org/github.com/d5/tengo/objects#Char), [Bytes](https://godoc.org/github.com/d5/tengo/objects#Bytes)
- Composite value types: [Array](https://godoc.org/github.com/d5/tengo/objects#Array), [Map](https://godoc.org/github.com/d5/tengo/objects#Map), [ImmutableMap](https://godoc.org/github.com/d5/tengo/objects#ImmutableMap)
- Functions: [CompiledFunction](https://godoc.org/github.com/d5/tengo/objects#CompiledFunction), [BuiltinFunction](https://godoc.org/github.com/d5/tengo/objects#BuiltinFunction), [UserFunction](https://godoc.org/github.com/d5/tengo/objects#UserFunction)
- [Iterators](https://godoc.org/github.com/d5/tengo/objects#Iterator): [StringIterator](https://godoc.org/github.com/d5/tengo/objects#StringIterator), [ArrayIterator](https://godoc.org/github.com/d5/tengo/objects#ArrayIterator), [MapIterator](https://godoc.org/github.com/d5/tengo/objects#MapIterator), [ImmutableMapIterator](https://godoc.org/github.com/d5/tengo/objects#ImmutableMapIterator)
- [Error](https://godoc.org/github.com/d5/tengo/objects#Error)
- [Undefined](https://godoc.org/github.com/d5/tengo/objects#Undefined)
- Other internal objects: [Closure](https://godoc.org/github.com/d5/tengo/objects#Closure), [CompiledModule](https://godoc.org/github.com/d5/tengo/objects#CompiledModule), [Break](https://godoc.org/github.com/d5/tengo/objects#Break), [Continue](https://godoc.org/github.com/d5/tengo/objects#Continue), [ReturnValue](https://godoc.org/github.com/d5/tengo/objects#ReturnValue)
### User Object Types
Basically Tengo runtime treats and manages both the runtime types and user types exactly the same way as long as they implement Object interface. You can add values of the custom user types (via either [Script.Add](https://godoc.org/github.com/d5/tengo/script#Script.Add) method or by directly manipulating the symbol table and the global variables), and, use them directly in Tengo code.
Here's an example user type, `Time`:
```golang
type Time struct {
Value time.Time
}
func (t *Time) TypeName() string {
return "time"
}
func (t *Time) String() string {
return t.Value.Format(time.RFC3339)
}
func (t *Time) BinaryOp(op token.Token, rhs objects.Object) (objects.Object, error) {
switch rhs := rhs.(type) {
case *Time:
switch op {
case token.Sub:
return &objects.Int{
Value: t.Value.Sub(rhs.Value).Nanoseconds(),
}, nil
}
case *objects.Int:
switch op {
case token.Add:
return &Time{
Value: t.Value.Add(time.Duration(rhs.Value)),
}, nil
case token.Sub:
return &Time{
Value: t.Value.Add(-time.Duration(rhs.Value)),
}, nil
}
}
return nil, objects.ErrInvalidOperator
}
func (t *Time) IsFalsy() bool {
return t.Value.IsZero()
}
func (t *Time) Equals(o objects.Object) bool {
if o, ok := o.(*Time); ok {
return t.Value.Equal(o.Value)
}
return false
}
func (t *Time) Copy() objects.Object {
return &Time{Value: t.Value}
}
```
Now the Tengo runtime recognizes `Time` type, and, any `Time` values can be used directly in the Tengo code:
```golang
s := script.New([]byte(`
a := currentTime + 10000 // Time + Int = Time
b := a - currentTime // Time - Time = Int
`))
// add Time value 'currentTime'
err := s.Add("currentTime", &Time{Value: time.Now()})
if err != nil {
panic(err)
}
c, err := s.Run()
if err != nil {
panic(err)
}
fmt.Println(c.Get("b")) // "10000"
```
## Callable Objects
Any types that implement [Callable](https://godoc.org/github.com/d5/tengo/objects#Callable) interface (in addition to Object interface), values of such types can be used as if they are functions.
```golang
type Callable interface {
Call(args ...Object) (ret Object, err error)
}
```
To make `Time` a callable value, add Call method to the previous implementation:
```golang
func (t *Time) Call(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
return nil, objects.ErrWrongNumArguments
}
format, ok := objects.ToString(args[0])
if !ok {
return nil, objects.ErrInvalidTypeConversion
}
return &objects.String{Value: t.Value.Format(format)}, nil
}
```
Now `Time` values can be "called" like this:
```golang
s := script.New([]byte(`
a := currentTime + 10000 // Time + Int = Time
b := a("15:04:05") // call 'a'
`))
// add Time value 'currentTime'
err := s.Add("currentTime", &Time{Value: time.Now()})
if err != nil {
panic(err)
}
c, err := s.Run()
if err != nil {
panic(err)
}
fmt.Println(c.Get("b")) // something like "21:15:27"
```
## Indexable Objects
If the type implements [Indexable](https://godoc.org/github.com/d5/tengo/objects#Indexable) interface, it enables dot selector (`value = object.index`) or indexer (`value = object[index]`) syntax for its values.
```golang
type Indexable interface {
IndexGet(index Object) (value Object, err error)
}
```
If the implementation returns an error (`err`), the VM will treat it as a run-time error. Many runtime types such as Map and Array also implement the same interface:
```golang
func (o *Map) IndexGet(index Object) (res Object, err error) {
strIdx, ok := index.(*String)
if !ok {
err = ErrInvalidIndexType
return
}
val, ok := o.Value[strIdx.Value]
if !ok {
val = UndefinedValue
}
return val, nil
}
```
Array and Map implementation forces the type of index Object (Int and String respectively), but, it's not required behavior by the VM. It is completely okay to take various index types (or to do type coercion) as long as its result is consistent.
By convention, Array or Array-like types return `ErrIndexOutOfBounds` error (as a runtime error) when the index is invalid (out of the bounds), and, Map or Map-like types return `Undefined` value when the key does not exist. But, again this is not a requirement, and, the type can implement the behavior however it fits.
## Index-Assignable Objects
If the type implements [IndexAssignable](https://godoc.org/github.com/d5/tengo/objects#IndexAssignable) interface, the values of that type allow assignment using dot selector (`object.index = value`) or indexer (`object[index] = value`) in the assignment statements.
```golang
type IndexAssignable interface {
IndexSet(index, value Object) error
}
```
Map, Array, and a couple of other runtime types also implement the same interface:
```golang
func (o *Map) IndexSet(index, value Object) (err error) {
strIdx, ok := ToString(index)
if !ok {
err = ErrInvalidTypeConversion
return
}
o.Value[strIdx] = value
return nil
}
```
Array and Map implementation forces the type of index Object (Int and String respectively), but, it's not required behavior by the VM. It is completely okay to take various index types (or to do type coercion) as long as its result is consistent.
By convention, Array or Array-like types return `ErrIndexOutOfBounds` error (as a runtime error) when the index is invalid (out of the bounds). But, this is not a requirement, and, the type can implement the behavior however it fits.
## Iterable Objects
Values of the types that implement [Iterable](https://godoc.org/github.com/d5/tengo/objects#Iterable) interface can be used in `for-in` statements (`for key, value in object { ... }`).
```golang
type Iterable interface {
Iterate() Iterator
}
```
This Iterate method should return another object that implements [Iterator](https://godoc.org/github.com/d5/tengo/objects#Iterator) interface.
### Iterator Interface
```golang
Next() bool
```
Next method should return true if there are more elements to iterate. When used with `for-in` statements, the compiler uses Key and Value methods to populate the current element's key (or index) and value from the object that this iterator represents. The runtime will stop iterating in `for-in` statement when this method returns false.
```golang
Key() Object
```
Key method should return a key (or an index) Object for the current element of the underlying object. It should return the same value until Next method is called again. By convention, iterators for the map or map-like objects returns the String key, and, iterators for array or array-like objects returns the Int index. But, it's not a requirement by the VM.
```golang
Value() Object
```
Value method should return a value Object for the current element of the underlying object. It should return the same value until Next method is called again.

69
docs/runtime-types.md Normal file
View file

@ -0,0 +1,69 @@
# Tengo Runtime Types
- **Int**: signed 64bit integer
- **String**: string
- **Float**: 64bit floating point
- **Bool**: boolean
- **Char**: character (`rune` in Go)
- **Bytes**: byte array (`[]byte` in Go)
- **Array**: objects array (`[]Object` in Go)
- **Map**: objects map with string keys (`map[string]Object` in Go)
- **ImmutableMap**: immutable object map with string keys (`map[string]Object` in Go)
- **Error**: an error with underlying Object value of any type
- **Undefined**: undefined
## Type Conversion/Coercion Table
|src\dst |Int |String |Float |Bool |Char |Bytes |Array |Map |IMap|Error |Undefined|
| :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: |
|Int | - |_strconv_ |float64(v)|!IsFalsy()| rune(v)|**X**|**X**|**X**|**X**|**X**|**X**|
|String |_strconv_| - |_strconv_|!IsFalsy()|**X**|[]byte(s)|**X**|**X**|**X**|**X**|**X**|
|Float |int64(f) |_strconv_ | - |!IsFalsy()|**X**|**X**|**X**|**X**|**X**|**X**|**X**|
|Bool |1 / 0 |"true" / "false"|**X** | - |**X**|**X**|**X**|**X**|**X**|**X**|**X**|
|Char |int64(c) |string(c) |**X** |!IsFalsy()| - |**X**|**X**|**X**|**X**|**X**|**X**|
|Bytes |**X** |string(y)|**X** |!IsFalsy()|**X**| - |**X**|**X**|**X**|**X**|**X**|
|Array |**X** |"[...]" |**X** |!IsFalsy()|**X**|**X**| - |**X**|**X**|**X**|**X**|
|Map |**X** |"{...}" |**X** |!IsFalsy()|**X**|**X**|**X**| - |**X**|**X**|**X**|
|IMap |**X** |"{...}" |**X** |!IsFalsy()|**X**|**X**|**X**|**X**| - |**X**|**X**|
|Error |**X** |"error: ..." |**X** |false|**X**|**X**|**X**|**X**|**X**| - |**X**|
|Undefined|**X** |**X**|**X** |false|**X**|**X**|**X**|**X**|**X**|**X**| - |
_* **X**: No conversion; Typed value functions for `script.Variable` will return zero values._
_* strconv: converted using Go's conversion functions from `strconv` package._
_* IsFalsy(): use [Object.IsFalsy()](#objectisfalsy) function_
## Object.IsFalsy()
`Object.IsFalsy()` interface method is used to determine if a given value should evaluate to `false` (e.g. for condition expression of `if` statement).
- **Int**: `n == 0`
- **String**: `len(s) == 0`
- **Float**: `isNaN(f)`
- **Bool**: `!b`
- **Char**: `c == 0`
- **Bytes**: `len(bytes) == 0`
- **Array**: `len(arr) == 0`
- **Map**: `len(map) == 0`
- **ImmutableMap**: `len(map) == 0`
- **Error**: `true` _(Error is always falsy)_
- **Undefined**: `true` _(Undefined is always falsy)_
## Type Conversion Builtin Functions
- `string(x)`: tries to convert `x` into string; returns `undefined` if failed
- `int(x)`: tries to convert `x` into int; returns `undefined` if failed
- `bool(x)`: tries to convert `x` into bool; returns `undefined` if failed
- `float(x)`: tries to convert `x` into float; returns `undefined` if failed
- `char(x)`: tries to convert `x` into char; returns `undefined` if failed
- `bytes(x)`: tries to convert `x` into bytes; returns `undefined` if failed
- `bytes(N)`: as a special case this will create a Bytes variable with the given size `N` (only if `N` is int)
## Type Checking Builtin Functions
- `is_string(x)`: returns `true` if `x` is string; `false` otherwise
- `is_int(x)`: returns `true` if `x` is int; `false` otherwise
- `is_bool(x)`: returns `true` if `x` is bool; `false` otherwise
- `is_float(x)`: returns `true` if `x` is float; `false` otherwise
- `is_char(x)`: returns `true` if `x` is char; `false` otherwise
- `is_bytes(x)`: returns `true` if `x` is bytes; `false` otherwise
- `is_error(x)`: returns `true` if `x` is error; `false` otherwise
- `is_undefined(x)`: returns `true` if `x` is undefined; `false` otherwise

28
docs/stdlib-exec.md Normal file
View file

@ -0,0 +1,28 @@
# Module - "exec"
```golang
exec := import("exec")
```
## Module Functions
- `look_path(file string) => string/error`: port of `exec.LookPath` function
- `command(name string, args array(string)) => `Cmd/error`: port of `exec.Command` function
## Cmd Functions
```golang
cmd := exec.command("echo", ["foo", "bar"])
output := cmd.output()
```
- `combined_output() => bytes/error`: port of `exec.Cmd.CombinedOutput` function
- `output() => bytes/error`: port of `exec.Cmd.Output` function
- `combined_output() => bytes/error`: port of `exec.Cmd.CombinedOutput` function
- `run() => error`: port of `exec.Cmd.Run` function
- `start() => error`: port of `exec.Cmd.Start` function
- `wait() => error`: port of `exec.Cmd.Wait` function
- `set_path(path string)`: sets `Path` of `exec.Cmd`
- `set_dir(dir string)`: sets `Dir` of `exec.Cmd`
- `set_env(env array(string))`: sets `Env` of `exec.Cmd`
- `process() => Process`: returns Process (`Process` of `exec.Cmd`)

79
docs/stdlib-math.md Normal file
View file

@ -0,0 +1,79 @@
# Module - "math"
```golang
math := import("math")
```
## Variables
- `e`: equivalent of Go's `math.E`
- `pi`: equivalent of Go's `math.Pi`
- `phi`: equivalent of Go's `math.Phi`
- `sqrt2`: equivalent of Go's `math.Sqrt2`
- `sqrtE`: equivalent of Go's `math.SqrtE`
- `sprtPi`: equivalent of Go's `math.SqrtPi`
- `sqrtPhi`: equivalent of Go's `math.SqrtPhi`
- `ln2`: equivalent of Go's `math.Ln2`
- `log2E`: equivalent of Go's `math.Log2E`
- `ln10`: equivalent of Go's `math.Ln10`
- `ln10E`: equivalent of Go's `math.Log10E`
## Functions
- `abs(float) => float`: port of Go's `math.Abs` function
- `acos(float) => float`: port of Go's `math.Acos` function
- `acosh(float) => float`: port of Go's `math.Acosh` function
- `asin(float) => float`: port of Go's `math.Asin` function
- `asinh(float) => float`: port of Go's `math.Asinh` function
- `atan(float) => float`: port of Go's `math.Atan` function
- `atan2(float, float) => float`: port of Go's `math.Atan2` function
- `atanh(float) => float`: port of Go's `math.Atanh` function
- `cbrt(float) => float`: port of Go's `math.Cbrt` function
- `ceil(float) => float`: port of Go's `math.Ceil` function
- `copysign(float, float) => float`: port of Go's `math.Copysign` function
- `cos(float) => float`: port of Go's `math.Cos` function
- `cosh(float) => float`: port of Go's `math.Cosh` function
- `dim(float, float) => float`: port of Go's `math.Dim` function
- `erf(float) => float`: port of Go's `math.Erf` function
- `erfc(float) => float`: port of Go's `math.Erfc` function
- `erfcinv(float) => float`: port of Go's `math.Erfcinv` function
- `erfinv(float) => float`: port of Go's `math.Erfinv` function
- `exp(float) => float`: port of Go's `math.Exp` function
- `exp2(float) => float`: port of Go's `math.Exp2` function
- `expm1(float) => float`: port of Go's `math.Expm1` function
- `floor(float) => float`: port of Go's `math.Floor` function
- `gamma(float) => float`: port of Go's `math.Gamma` function
- `hypot(float, float) => float`: port of Go's `math.Hypot` function
- `ilogb(float) => float`: port of Go's `math.Ilogb` function
- `inf(int) => float`: port of Go's `math.Inf` function
- `is_inf(float, int) => float`: port of Go's `math.IsInf` function
- `is_nan(float) => float`: port of Go's `math.IsNaN` function
- `j0(float) => float`: port of Go's `math.J0` function
- `j1(float) => float`: port of Go's `math.J1` function
- `jn(int, float) => float`: port of Go's `math.Jn` function
- `ldexp(float, int) => float`: port of Go's `math.Ldexp` function
- `log(float) => float`: port of Go's `math.Log` function
- `log10(float) => float`: port of Go's `math.Log10` function
- `log1p(float) => float`: port of Go's `math.Log1p` function
- `log2(float) => float`: port of Go's `math.Log2` function
- `logb(float) => float`: port of Go's `math.Logb` function
- `max(float, float) => float`: port of Go's `math.Max` function
- `min(float, float) => float`: port of Go's `math.Min` function
- `mod(float, float) => float`: port of Go's `math.Mod` function
- `nan() => float`: port of Go's `math.NaN` function
- `nextafter(float, float) => float`: port of Go's `math.Nextafter` function
- `pow(float, float) => float`: port of Go's `math.Pow` function
- `pow10(int) => float`: port of Go's `math.Pow10` function
- `remainder(float, float) => float`: port of Go's `math.Remainder` function
- `round(float) => float`: port of Go's `math.Round` function
- `round_to_even(float) => float`: port of Go's `math.RoundToEven` function
- `signbit(float) => float`: port of Go's `math.Signbit` function
- `sin(float) => float`: port of Go's `math.Sin` function
- `sinh(float) => float`: port of Go's `math.Sinh` function
- `sqrt(float) => float`: port of Go's `math.Sqrt` function
- `tan(float) => float`: port of Go's `math.Tan` function
- `tanh(float) => float`: port of Go's `math.Tanh` function
- `runct(float) => float`: port of Go's `math.Trunc` function
- `y0(float) => float`: port of Go's `math.Y0` function
- `y1(float) => float`: port of Go's `math.Y1` function
- `yn(int, float) => float`: port of Go's `math.Yn` function

125
docs/stdlib-os.md Normal file
View file

@ -0,0 +1,125 @@
# Module - "os"
```golang
os := import("os")
```
## Module Variables
- `o_rdonly`: equivalent of Go's `os.O_RDONLY`
- `o_wronly`: equivalent of Go's `os.O_WRONLY`
- `o_rdwr`: equivalent of Go's `os.O_RDWR`
- `o_append`: equivalent of Go's `os.O_APPEND`
- `o_create`: equivalent of Go's `os.O_CREATE`
- `o_excl`: equivalent of Go's `os.O_EXCL`
- `o_sync`: equivalent of Go's `os.O_SYNC`
- `o_trunc`: equivalent of Go's `os.O_TRUNC`
- `mode_dir`: equivalent of Go's `os.ModeDir`
- `mode_append`: equivalent of Go's `os.ModeAppend`
- `mode_exclusive`: equivalent of Go's `os.ModeExclusive`
- `mode_temporary`: equivalent of Go's `os.ModeTemporary`
- `mode_symlink`: equivalent of Go's `os.ModeSymlink`
- `mode_device`: equivalent of Go's `os.ModeDevice`
- `mode_named_pipe`: equivalent of Go's `os.ModeNamedPipe`
- `mode_socket`: equivalent of Go's `os.ModeSocket`
- `mode_setuid`: equivalent of Go's `os.ModeSetuid`
- `mode_setgui`: equivalent of Go's `os.ModeSetgid`
- `mode_char_device`: equivalent of Go's `os.ModeCharDevice`
- `mode_sticky`: equivalent of Go's `os.ModeSticky`
- `mode_irregular`: equivalent of Go's `os.ModeIrregular`
- `mode_type`: equivalent of Go's `os.ModeType`
- `mode_perm`: equivalent of Go's `os.ModePerm`
- `seek_set`: equivalent of Go's `os.SEEK_SET`
- `seek_cur`: equivalent of Go's `os.SEEK_CUR`
- `seek_end`: equivalent of Go's `os.SEEK_END`
- `path_separator`: equivalent of Go's `os.PathSeparator`
- `path_list_separator`: equivalent of Go's `os.PathListSeparator`
- `dev_null`: equivalent of Go's `os.DevNull`
## Module Functions
- `args() => array(string)`: returns `os.Args`
- `chdir(dir string) => error`: port of `os.Chdir` function
- `chmod(name string, mode int) => error `: port of Go's `os.Chmod` function
- `chown(name string, uid int, gid int) => error `: port of Go's `os.Chown` function
- `clearenv() `: port of Go's `os.Clearenv` function
- `environ() => array(string) `: port of Go's `os.Environ` function
- `executable() => string/error`: port of Go's `os.Executable()` function
- `exit(code int) `: port of Go's `os.Exit` function
- `expand_env(s string) => string `: port of Go's `os.ExpandEnv` function
- `getegid() => int `: port of Go's `os.Getegid` function
- `getenv(s string) => string `: port of Go's `os.Getenv` function
- `geteuid() => int `: port of Go's `os.Geteuid` function
- `getgid() => int `: port of Go's `os.Getgid` function
- `getgroups() => array(string)/error `: port of Go's `os.Getgroups` function
- `getpagesize() => int `: port of Go's `os.Getpagesize` function
- `getpid() => int `: port of Go's `os.Getpid` function
- `getppid() => int `: port of Go's `os.Getppid` function
- `getuid() => int `: port of Go's `os.Getuid` function
- `getwd() => string/error `: port of Go's `os.Getwd` function
- `hostname() => string/error `: port of Go's `os.Hostname` function
- `lchown(name string, uid int, gid int) => error `: port of Go's `os.Lchown` function
- `link(oldname string, newname string) => error `: port of Go's `os.Link` function
- `lookup_env(key string) => string/false`: port of Go's `os,LookupEnv` function
- `mkdir(name string, perm int) => error `: port of Go's `os.Mkdir` function
- `mkdir_all(name string, perm int) => error `: port of Go's `os.MkdirAll` function
- `readlink(name string) => string/error `: port of Go's `os.Readlink` function
- `remove(name string) => error `: port of Go's `os.Remove` function
- `remove_all(name string) => error `: port of Go's `os.RemoveAll` function
- `rename(oldpath string, newpath string) => error `: port of Go's `os.Rename` function
- `setenv(key string, value string) => error `: port of Go's `os.Setenv` function
- `symlink(oldname string newname string) => error `: port of Go's `os.Symlink` function
- `temp_dir() => string `: port of Go's `os.TempDir` function
- `truncate(name string, size int) => error `: port of Go's `os.Truncate` function
- `unsetenv(key string) => error `: port of Go's `os.Unsetenv` function
- `user_cache_dir() => string/error `: port of Go's `os.UserCacheDir` function
- `create(name string) => File/error`: port of Go's `os.Create` function
- `open(name string) => File/error`: port of Go's `os.Open` function
- `open_file(name string, flag int, perm int) => File/error`: port of Go's `os.OpenFile` function
- `find_process(pid int) => Process/error`: port of Go's `os.FindProcess` function
- `start_process(name string, argv array(string), dir string, env array(string)) => Process/error`: port of Go's `os.StartProcess` function
## File Functions
```golang
file := os.create("myfile")
file.write_string("some data")
file.close()
```
- `chdir() => true/error`: port of `os.File.Chdir` function
- `chown(uid int, gid int) => true/error`: port of `os.File.Chown` function
- `close() => error`: port of `os.File.Close` function
- `name() => string`: port of `os.File.Name` function
- `readdirnames() => array(string)/error`: port of `os.File.Readdirnames` function
- `sync() => error`: port of `os.File.Sync` function
- `write(bytes) => int/error`: port of `os.File.Write` function
- `write_string(string) => int/error`: port of `os.File.WriteString` function
- `read(bytes) => int/error`: port of `os.File.Read` function
- `chmod(mode int) => error`: port of `os.File.Chmod` function
- `seek(offset int, whence int) => int/error`: port of `os.File.Seek` function
## Process Functions
```golang
proc := start_process("app", ["arg1", "arg2"], "dir", [])
proc.wait()
```
- `kill() => error`: port of `os.Process.Kill` function
- `release() => error`: port of `os.Process.Release` function
- `signal(signal int) => error`: port of `os.Process.Signal` function
- `wait() => ProcessState/error`: port of `os.Process.Wait` function
## ProcessState Functions
```golang
proc := start_process("app", ["arg1", "arg2"], "dir", [])
stat := proc.wait()
pid := stat.pid()
```
- `exited() => bool`: port of `os.ProcessState.Exited` function
- `pid() => int`: port of `os.ProcessState.Pid` function
- `string() => string`: port of `os.ProcessState.String` function
- `success() => bool`: port of `os.ProcessState.Success` function

7
docs/stdlib.md Normal file
View file

@ -0,0 +1,7 @@
# Standard Library
_Warning: standard library implementations/interfaces are **experimental** and subject to change in the future release._
- [math](https://github.com/d5/tengo/blob/master/docs/stdlib-math.md)
- [os](https://github.com/d5/tengo/blob/master/docs/stdlib-os.md)
- [exec](https://github.com/d5/tengo/blob/master/docs/stdlib-exec.md)

34
docs/tengo-cli.md Normal file
View file

@ -0,0 +1,34 @@
# Tengo CLI Tool
Tengo is designed as an embedding script language for Go, but, it can also be compiled and executed as native binary using `tengo` CLI tool.
## Installing Tengo CLI
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/Readevalprint_loop) if you run `tengo` with no arguments.
```bash
tengo
```

193
docs/tutorial.md Normal file
View file

@ -0,0 +1,193 @@
# Tengo Syntax
Tengo's syntax is designed to be familiar to Go developers while being a bit simpler and more streamlined.
## Comments
Tengo supports line comments (`//...`) and block comments (`/* ... */`).
```golang
/*
multi-line block comments
*/
a := 5 // line comments
```
> [Run in Playground](https://tengolang.com/?s=02e384399a0397b0a752f08604ccb244d1a6cb37)
## Types and Assignment
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
}
```
> [Run in Playground](https://tengolang.com/?s=f8626a711769502ce20e4560ace65c0e9c1279f4)
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.
}
a == "foo" // still "foo"
```
> [Run in Playground](https://tengolang.com/?s=1d39bc2af5c51417df82b32db47a0e6a156d48ec)
Type is not directly 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'
```
> [Run in Playground](https://tengolang.com/?s=8d57905b82959eb244e9bbd2111e12ee04a33045)
_See [Variable Types](https://github.com/d5/tengo/wiki/Variable-Types) for more details on the variable types._
## Indexing
You can use the dot selector (`.`) and indexer (`[]`) operator to read or write elements of arrays, strings, 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
```
> [Run in Playground](https://tengolang.com/?s=d510c75ed8f06ef1e22c1aaf8a7d4565c793514c)
For sequence types (string, bytes, array), you can use slice operator (`[:]`) too.
```golang
a := [1, 2, 3, 4, 5][1:3] // == [2, 3]
b := [1, 2, 3, 4, 5][3:] // == [4, 5]
c := [1, 2, 3, 4, 5][:3] // == [1, 2, 3]
d := "hello world"[2:10] // == "llo worl"
```
> [Run in Playground](https://tengolang.com/?s=214ab490bb24549578770984985f6b161aed915d)
## Functions
In Tengo, functions are first-class citizen, and, it also supports closures, functions that captures variables in outer scopes. In the following example, the function returned from `adder` is capturing `base` variable.
```golang
adder := func(base) {
return func(x) { return base + x } // capturing 'base'
}
add5 := adder(5)
nine := add5(4) // == 9
```
> [Run in Playground](https://tengolang.com/?s=fba79990473d5b38cc944dfa225d38580ddaf422)
## Flow Control
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
// ...
}
```
## Errors
An error object is created using `error` function-like keyword. An error can have any types of value and the underlying value of the error can be accessed using `.value` selector.
```golang
err1 := error("oops") // error with string value
err2 := error(1+2+3) // error with int value
if is_error(err1) { // 'is_error' builtin function
err_val := err1.value // get underlying value
}
```
> [Run in Playground](https://tengolang.com/?s=5eaba4289c9d284d97704dd09cb15f4f03ad05c1)
## Modules
You can load other scripts as modules using `import` expression.
Main script:
```golang
mod1 := import("./mod1") // assuming mod1.tengo file exists in the current directory
// same as 'import("./mod1.tengo")' or 'import("mod1")'
mod1.func1(a) // module function
a += mod1.foo // module variable
//mod1.foo = 5 // error: module variables are read-only
```
`mod1.tengo` file:
```golang
func1 := func(x) { print(x) }
foo := 2
```
Basically, `import` expression returns all the global variables defined in the module as a Map-like value. One can access the functions or variables defined in the module using `.` selector or `["key"]` indexer, but, module variables are immutable.
Also, you can use `import` to load the [Standard Library](https://github.com/d5/tengo/blob/master/docs/stdlib.md).
```golang
math := import("math")
a := math.abs(-19.84) // == 19.84
```