Skip to content

Commit

Permalink
Add mulw
Browse files Browse the repository at this point in the history
  • Loading branch information
algorandskiy committed Dec 5, 2019
1 parent 93502cb commit dd46a37
Show file tree
Hide file tree
Showing 11 changed files with 265 additions and 35 deletions.
11 changes: 11 additions & 0 deletions GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,17 @@ let sender = if global.GroupSize > 1 { txn.Sender } else { gtxn[1].Sender }
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 functions

`sha256`, `keccak256`, `sha512_256`, `ed25519verify`, `len`, `itob`, `btoi`, `mulw` are supported.
The latter is a special one - it returns two values, high and low.

```
let h = len("123")
let l = btoi("1")
h, l = mulw(l, h)
```

## Builtin objects

There are 4 builtin objects: `txn`, `gtxn`, `global`, `args`. Accessing them is an expression.
Expand Down
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,5 +126,4 @@ make java-gui

## Limitations

* No `mulw` **TEAL** opcode support.
* No check that all branches `return` or `error`.
7 changes: 6 additions & 1 deletion TealangLexer.l4
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ BUILTINFUNC
| KECCAK256
| SHA512
| ED25519
| MULW
| LEN
| ITOB
| BTOI
;

LET : 'let' ;
Expand Down Expand Up @@ -93,6 +95,9 @@ KECCAK256 : 'keccak256' ;
SHA512 : 'sha512_256' ;
ED25519 : 'ed25519verify' ;
MULW : 'mulw';
LEN : 'len';
ITOB : 'itob';
BTOI : 'btoi';

STRING : EncodingPrefix? '"' StringChar* '"' ;
NUMBER : [0-9]+ ;
Expand Down
8 changes: 7 additions & 1 deletion TealangParser.g4
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,14 @@ termination

decl
: LET IDENT EQ expr # DeclareVar
| LET IDENT COMMA IDENT EQ mulwCall # DeclareVarMulw
| CONST IDENT EQ NUMBER # DeclareNumberConst
| CONST IDENT EQ STRING # DeclareStringConst
;

assignment
: IDENT '=' expr # Assign
: IDENT EQ expr # Assign
| IDENT COMMA IDENT EQ mulwCall # AssignMulw
;

expr
Expand All @@ -84,6 +86,10 @@ expr
| condExpr # IfExpr
;

mulwCall
: MULW LEFTPARA (expr COMMA expr ) RIGHTPARA
;

functionCall
: BUILTINFUNC LEFTPARA (expr (COMMA expr)* )? RIGHTPARA # BuiltinFunCall
| IDENT LEFTPARA (expr (COMMA expr)* )? RIGHTPARA # FunCall
Expand Down
64 changes: 43 additions & 21 deletions compiler/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,18 +71,6 @@ func newContext(parent *context) (ctx *context) {
return
}

// for future use
var builtins = map[string]bool{
"global": true,
"txn": true,
"gtxn": true,
"args": true,
"sha256": true,
"sha512_256": true,
"keccak256": true,
"ed25519verify": true,
}

func (ctx *context) lookup(name string) (varable varInfo, err error) {
current := ctx
for current != nil {
Expand All @@ -109,9 +97,6 @@ func (ctx *context) update(name string, info varInfo) (err error) {
}

func (ctx *context) newVar(name string, theType exprType) error {
if _, ok := builtins[name]; ok {
return fmt.Errorf("%s is builtin", name)
}
if _, ok := ctx.vars[name]; ok {
return fmt.Errorf("variable '%s' already declared", name)
}
Expand All @@ -121,9 +106,6 @@ func (ctx *context) newVar(name string, theType exprType) error {
}

func (ctx *context) newConst(name string, theType exprType, value *string) error {
if _, ok := builtins[name]; ok {
return fmt.Errorf("%s is builtin", name)
}
if _, ok := ctx.vars[name]; ok {
return fmt.Errorf("const '%s' already declared", name)
}
Expand All @@ -136,9 +118,6 @@ func (ctx *context) newConst(name string, theType exprType, value *string) error
}

func (ctx *context) newFunc(name string, theType exprType, parser func(listener *treeNodeListener, callNode *funCallNode)) error {
if _, ok := builtins[name]; ok {
return fmt.Errorf("%s is builtin", name)
}
if _, ok := ctx.vars[name]; ok {
return fmt.Errorf("function '%s' already defined", name)
}
Expand Down Expand Up @@ -207,6 +186,7 @@ var builtinFun = map[string]bool{
"len": true,
"itob": true,
"btoi": true,
"mulw": true,
}

// TreeNodeIf represents a node in AST
Expand Down Expand Up @@ -265,13 +245,29 @@ type assignNode struct {
value ExprNodeIf
}

type assignMulwNode struct {
*TreeNode
low string
high string
exprType exprType
value ExprNodeIf
}

type varDeclNode struct {
*TreeNode
name string
exprType exprType
value ExprNodeIf
}

type varDeclMulwNode struct {
*TreeNode
low string
high string
exprType exprType
value ExprNodeIf
}

type constNode struct {
*TreeNode
name string
Expand Down Expand Up @@ -396,6 +392,16 @@ func newAssignNode(ctx *context, parent TreeNodeIf, ident string) (node *assignN
return
}

func newAssignMulwNode(ctx *context, parent TreeNodeIf, identLow string, identHigh string) (node *assignMulwNode) {
node = new(assignMulwNode)
node.TreeNode = newNode(ctx, parent)
node.nodeName = "assign mulw"
node.low = identLow
node.high = identHigh
node.value = nil
return
}

func newFunDefNode(ctx *context, parent TreeNodeIf) (node *funDefNode) {
node = new(funDefNode)
node.TreeNode = newNode(ctx, parent)
Expand All @@ -414,6 +420,18 @@ func newVarDeclNode(ctx *context, parent TreeNodeIf, ident string, value ExprNod
return
}

func newVarDeclMulwNode(ctx *context, parent TreeNodeIf, identLow string, identHigh string, value ExprNodeIf) (node *varDeclMulwNode) {
node = new(varDeclMulwNode)
node.TreeNode = newNode(ctx, parent)
node.nodeName = "var mulw"
node.low = identLow
node.high = identHigh
node.value = value
tp, _ := value.getType()
node.exprType = tp
return
}

func newConstNode(ctx *context, parent TreeNodeIf, ident string, value string, exprType exprType) (node *constNode) {
node = new(constNode)
node.TreeNode = newNode(ctx, parent)
Expand Down Expand Up @@ -805,6 +823,10 @@ func (n *varDeclNode) String() string {
return fmt.Sprintf("var (%s) %s = %s", n.exprType, n.name, n.value)
}

func (n *varDeclMulwNode) String() string {
return fmt.Sprintf("var (%s) %s, %s = %s", n.exprType, n.high, n.low, n.value)
}

func (n *constNode) String() string {
return fmt.Sprintf("const (%s) %s = %s", n.exprType, n.name, n.value)
}
Expand Down
37 changes: 35 additions & 2 deletions compiler/ast_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -483,7 +483,7 @@ function logic() {
a.Empty(result)
a.NotEmpty(parserErrors)
a.Equal(3, len(parserErrors), parserErrors)
a.Contains(parserErrors[0].msg, `mismatched input 'global' expecting IDENT`)
a.Contains(parserErrors[0].msg, `no viable alternative at input 'let global'`)
a.Contains(parserErrors[1].msg, `no viable alternative at input 'const gtxn'`)
a.Contains(parserErrors[2].msg, `no viable alternative at input 'function txn'`)

Expand All @@ -509,5 +509,38 @@ function logic() {
a.NotEmpty(parserErrors)
a.Equal(2, len(parserErrors), parserErrors)
a.Contains(parserErrors[0].msg, `no viable alternative at input 'const sha512_256'`)
a.Contains(parserErrors[1].msg, `mismatched input 'args' expecting IDENT`)
a.Contains(parserErrors[1].msg, `no viable alternative at input 'let args'`)
}

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

source := `
function logic() {
let a = sha256("test", 1)
}
`
result, parserErrors := Parse(source)
a.Empty(result)
a.NotEmpty(parserErrors)
a.Equal(1, len(parserErrors), parserErrors)
a.Contains(parserErrors[0].msg, `can't get type for sha256 arg #2`)
}

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

source := `
let a, b = mulw(1, 2)
function logic() {
a, b = mulw(1, 2)
if a == b {
return 0
}
return 1
}
`
result, parserErrors := Parse(source)
a.NotEmpty(result, parserErrors)
a.Empty(parserErrors)
}
18 changes: 18 additions & 0 deletions compiler/codegen.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,15 @@ func (n *assignNode) Codegen(ostream io.Writer) {
fmt.Fprintf(ostream, "store %d\n", info.address)
}

func (n *assignMulwNode) Codegen(ostream io.Writer) {
n.value.Codegen(ostream)

info, _ := n.ctx.lookup(n.low)
fmt.Fprintf(ostream, "store %d\n", info.address)
info, _ = n.ctx.lookup(n.high)
fmt.Fprintf(ostream, "store %d\n", info.address)
}

func (n *returnNode) Codegen(ostream io.Writer) {
n.value.Codegen(ostream)
fmt.Fprintf(
Expand Down Expand Up @@ -130,6 +139,15 @@ func (n *varDeclNode) Codegen(ostream io.Writer) {
fmt.Fprintf(ostream, "store %d\n", info.address)
}

func (n *varDeclMulwNode) Codegen(ostream io.Writer) {
n.value.Codegen(ostream)

info, _ := n.ctx.lookup(n.low)
fmt.Fprintf(ostream, "store %d\n", info.address)
info, _ = n.ctx.lookup(n.high)
fmt.Fprintf(ostream, "store %d\n", info.address)
}

func (n *runtimeFieldNode) Codegen(ostream io.Writer) {
if n.op == "gtxn" {
fmt.Fprintf(ostream, "%s %s %s\n", n.op, n.index, n.field)
Expand Down
33 changes: 33 additions & 0 deletions compiler/codegen_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -386,3 +386,36 @@ function logic() {
a.Equal("bnz end_logic", lines[6])
a.Equal("end_logic:", lines[7])
}

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

source := `
let h, l = mulw(1, 2)
function logic() {
h, l = mulw(3, 4)
return l
}
`
result, errors := Parse(source)
a.NotEmpty(result, errors)
a.Empty(errors)
prog := Codegen(result)
fmt.Print(prog)
lines := strings.Split(prog, "\n")
a.Equal("intcblock 0 1 2 3 4", lines[0])
a.Equal("intc 1", lines[1])
a.Equal("intc 2", lines[2])
a.Equal("mulw", lines[3])
a.Equal("store 0", lines[4]) // store low
a.Equal("store 1", lines[5]) // store high
a.Equal("intc 3", lines[6])
a.Equal("intc 4", lines[7])
a.Equal("mulw", lines[8])
a.Equal("store 0", lines[9])
a.Equal("store 1", lines[10])
a.Equal("load 0", lines[11])
a.Equal("intc 1", lines[12])
a.Equal("bnz end_logic", lines[13])
a.Equal("end_logic:", lines[14])
}
4 changes: 4 additions & 0 deletions compiler/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ function logic() {
return inc(0);
let h = len("123")
let l = btoi("1")
h, l = mulw(l, h)
let ret = TxTypePayment
return ret
}
Expand Down
2 changes: 1 addition & 1 deletion compiler/langspec.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func argOpTypeFromSpec(op string, arg int) (exprType, error) {
return unknownType, nil
}
}
return invalidType, fmt.Errorf("can't get type for %s arg #%d", op, arg)
return invalidType, fmt.Errorf("can't get type for %s arg #%d", op, arg+1)
}

func runtimeFieldTypeFromSpec(op string, field string) (exprType, error) {
Expand Down
Loading

0 comments on commit dd46a37

Please sign in to comment.