Skip to content

Commit

Permalink
Add tealang guide
Browse files Browse the repository at this point in the history
* Fix tabs to spaces in tl sources
  • Loading branch information
algorandskiy committed Dec 4, 2019
1 parent 360d87f commit 21c7ed6
Show file tree
Hide file tree
Showing 8 changed files with 382 additions and 84 deletions.
197 changes: 197 additions & 0 deletions GUIDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
# Tealang guide

Tealang translates all (except `mulw`) own constructions to corresponding **TEAL** instructions
so that there is almost one-to-one mapping between statements in both languages.

Refer to [TEAL documentation](https://developer.algorand.org/docs/teal) for details.

*Note, all code snippets below only for Tealang features demonstration.*

## Program structure

* imports
* variable and constant declarations and function definitions
* logic function

## Types

* uint64 (unsigned integer)
* []byte (byte array)

## Statements vs expressions

Statement is a standalone unit of execution that does not return any value.
In opposite, expression is evaluated to some value.

## Declarations, definitions and assignments

Constants, variables and function are supported:
```
const a = 1
const b = "abc\x01"
let x = b
function test(x) { return x; }
```

Declarations, definitions and assignments are statement

## Functions

Unlike **TEAL** functions are supported and inlined at the calling point.
Functions must return some value and can not be recursive.

```
function inc(x) { return x+1; }
function logic() { return inc(0); }
```

## Logic function

Must exist in every program and return integer. The return value (zero/non-zero) is **TRUE** or **FALSE** return code for entire **TEAL** program (smart contract).
```
function logic() {
if txn.Sender == "ABC" {
return 1
}
return 0
}
```

## Flow control statements

### if-else

Consist of `if` keyword, conditional expression (must evaluate to integer), `if-block` and optional `else-block`.
```
if x == 1 {
return 1
} else {
let x = txn.Receiver
}
```

### return

`return` forces current function to exit and return a value. For the special `logic` function it would be entire program return value.

### error

`error` forces program to exit with an error.

## Comments

Single line comments are supported, commenting sequence is `//`

## Expressions

### Conditional expression

Similar to conditional statement but `else-block` is required, and both blocks must evaluate to an expression.
```
let sender = if global.GroupSize > 1 { txn.Sender } else { gtxn[1].Sender }
```

### Arithmetic, Logic, and Cryptographic Operations

All operations like +, -, *, ==, !=, <, >, >=, etc.
See [TEAL documentation](https://github.com/algorand/go-algorand/blob/master/data/transactions/logic/README.md#arithmetic-logic-and-cryptographic-operations) for the full list.

## Builtin objects

There are 4 builtin objects: `txn`, `gtxn`, `global`, `args`. Accessing them is an expression.

| Object and Syntax | Description |
| --- | --- |
| `args[N]` | returns Args[N] value |
| `txn.FIELD` | retrieves field from current transaction |
| `gtxn[N].FIELD` | retrieves field from a transaction N in the current transaction group |
| `global.FIELD` | returns globals |

#### Transaction fields
| Index | Name | Type | Notes |
| --- | --- | --- | --- |
| 0 | Sender | []byte | 32 byte address |
| 1 | Fee | uint64 | micro-Algos |
| 2 | FirstValid | uint64 | round number |
| 3 | FirstValidTime | uint64 | Causes program to fail; reserved for future use. |
| 4 | LastValid | uint64 | round number |
| 5 | Note | []byte | |
| 6 | Lease | []byte | |
| 7 | Receiver | []byte | 32 byte address |
| 8 | Amount | uint64 | micro-Algos |
| 9 | CloseRemainderTo | []byte | 32 byte address |
| 10 | VotePK | []byte | 32 byte address |
| 11 | SelectionPK | []byte | 32 byte address |
| 12 | VoteFirst | uint64 | |
| 13 | VoteLast | uint64 | |
| 14 | VoteKeyDilution | uint64 | |
| 15 | Type | []byte | |
| 16 | TypeEnum | uint64 | See table below |
| 17 | XferAsset | uint64 | Asset ID |
| 18 | AssetAmount | uint64 | value in Asset's units |
| 19 | AssetSender | []byte | 32 byte address. Causes clawback of all value of asset from AssetSender if Sender is the Clawback address of the asset. |
| 20 | AssetReceiver | []byte | 32 byte address |
| 21 | AssetCloseTo | []byte | 32 byte address |
| 22 | GroupIndex | uint64 | Position of this transaction within an atomic transaction group. A stand-alone transaction is implicitly element 0 in a group of 1. |
| 23 | TxID | []byte | The computed ID for this transaction. 32 bytes. |

#### Global fields

| Index | Name | Type | Notes |
| --- | --- | --- | --- |
| 0 | MinTxnFee | uint64 | micro Algos |
| 1 | MinBalance | uint64 | micro Algos |
| 2 | MaxTxnLife | uint64 | rounds |
| 3 | ZeroAddress | []byte | 32 byte address of all zero bytes |
| 4 | GroupSize | uint64 | Number of transactions in this atomic transaction group. At least 1. |

## Scopes

Tealang maintains a single global scope (shared between main program and imported modules) and nested scopes for every execution block.
Blocks are created for functions and if-else branches.
Parent scope is accessible from nested blocks. If a variable declared in nested block, it might shadow variable with the same name from parent scope.

```
let x = 1
function logic() {
let x = 2 // shadows 1 in logic block
if 1 {
let x = 3 // shadows 2 in if-block
}
return x // 2
}
```

## Imports

Unlike **TEAL**, a tealang program can be split to modules. There is a standard library `stdlib` containing some constants and **TEAL** templates as tealang functions.

### Module structure

* imports
* declarations and definitions

```
const myconst = 1
function myfunction() { return 0; }
```

## Standard library

At the moment consist of 2 files:
1. const.tl
2. template.tl

```
import stdlib.const
function logic() {
let ret = TxTypePayment
return ret
}
```

## More examples

* [examples directory](https://github.com/pzbitskiy/tealang/tree/master/examples)
* [stdlib directory](https://github.com/pzbitskiy/tealang/tree/master/stdlib)
29 changes: 24 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Teal Language

High-level language for Algorand Smart Contracts at Layer-1 and it's low-level TEAL language.
The goal is to abstract the stack-based TEAL VM and provide imperative Go/JS/Python-like syntax.
High-level language for Algorand Smart Contracts at Layer-1 and it's low-level **TEAL** language.
The goal is to abstract the stack-based **TEAL** VM and provide imperative Go/JS/Python-like syntax.

## Language Features

Expand All @@ -13,7 +13,7 @@ let variable1 = 1
const myaddr = "XYZ"
```

* All binary and unary operations from TEAL
* All binary and unary operations from **TEAL**
```
let a = (1 + 2) / 3
let b = ~a
Expand Down Expand Up @@ -55,8 +55,17 @@ function logic() {
}
```

* Modules
```
import stdlib.const
```

* Antlr-based parser

## Language guide

[Documentation](GUIDE.md)

## Usage

* Tealang to bytecode
Expand All @@ -79,8 +88,6 @@ function logic() {

Checkout [syntax highlighter](https://github.com/pzbitskiy/tealang-syntax-highlighter) for vscode.

TODO: Tealang guide

## Build from sources

### Prerequisites
Expand Down Expand Up @@ -109,3 +116,15 @@ make go
```sh
make java-gui
```

## Roadmap

1. Constant folding.
2. Strings concatenation.
3. Fix known limitations.
4. Code generation for return at the end of function

## Limitations

* No `mulw` **TEAL** opcode support.
* No check that all branches `return` or `error`.
63 changes: 63 additions & 0 deletions compiler/codegen_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -299,3 +299,66 @@ func TestCodegenOneLineCond(t *testing.T) {
a.Equal("==", lines[9])
a.Equal("&&", lines[10])
}

func TestCodegenShadow(t *testing.T) {
a := require.New(t)

source := `
let x = 1
function logic() {
let x = 2 // shadows 1 in logic block
if 1 {
let x = 3 // shadows 2 in if-block
}
return x // 2
}
`
result, errors := Parse(source)
a.NotEmpty(result, errors)
a.Empty(errors)
prog := Codegen(result)
lines := strings.Split(prog, "\n")
a.Equal("intcblock 0 1 2 3", lines[0])
a.Equal("intc 1", lines[1])
a.Equal("store 0", lines[2])
a.Equal("intc 2", lines[3])
a.Equal("store 1", lines[4])
a.Equal("intc 1", lines[5])
a.Equal("!", lines[6])
a.Equal("bnz if_stmt_end_", lines[7][:len("bnz if_stmt_end_")])
a.Equal("intc 3", lines[8])
a.Equal("store 2", lines[9])
a.Equal("if_stmt_end_", lines[10][:len("if_stmt_end_")])
a.Equal("load 1", lines[11])
a.Equal("intc 1", lines[12])
a.Equal("bnz end_logic", lines[13])
a.Equal("end_logic:", lines[14])
}

func TestCodegenNestedFun(t *testing.T) {
a := require.New(t)

source := `
function test1() { return 1; }
function test2() { return test1(); }
function logic() {
return test2()
}
`
result, errors := Parse(source)
a.NotEmpty(result, errors)
a.Empty(errors)
prog := Codegen(result)
lines := strings.Split(prog, "\n")
a.Equal("intcblock 0 1", lines[0])
a.Equal("intc 1", lines[1])
a.Equal("intc 1", lines[2])
a.Equal("bnz end_test1", lines[3])
a.Equal("end_test1:", lines[4])
a.Equal("intc 1", lines[5])
a.Equal("bnz end_test2", lines[6])
a.Equal("end_test2:", lines[7])
a.Equal("intc 1", lines[8])
a.Equal("bnz end_logic", lines[9])
a.Equal("end_logic:", lines[10])
}
45 changes: 45 additions & 0 deletions compiler/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,48 @@ function logic() {
prog := Codegen(result)
a.NotEmpty(prog)
}

func TestGuideCompile(t *testing.T) {
a := require.New(t)
source := `
import stdlib.const
const a = 1
const b = "abc\x01"
let x = b
function test(x) { return x; }
let sender = if global.GroupSize > 1 { txn.Sender } else { gtxn[1].Sender }
function inc(x) { return x+1; }
const myconst = 1
function myfunction() { return 0; }
function logic() {
if txn.Sender == "ABC" {
return 1
}
let x = 2 // shadows 1 in logic block
if 1 {
let x = 3 // shadows 2 in if-block
}
return x // 2
if x == 1 {
return 1
} else {
let x = txn.Receiver
}
return inc(0);
let ret = TxTypePayment
return ret
}
`
result, errors := Parse(source)
a.NotEmpty(result, errors)
a.Empty(errors)
prog := Codegen(result)
a.NotEmpty(prog)
}
Loading

0 comments on commit 21c7ed6

Please sign in to comment.