diff --git a/GUIDE.md b/GUIDE.md index e0f048d..da0fccb 100644 --- a/GUIDE.md +++ b/GUIDE.md @@ -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. diff --git a/README.md b/README.md index 7e35d28..6d4e677 100644 --- a/README.md +++ b/README.md @@ -126,5 +126,4 @@ make java-gui ## Limitations -* No `mulw` **TEAL** opcode support. * No check that all branches `return` or `error`. diff --git a/TealangLexer.l4 b/TealangLexer.l4 index 31818aa..ef6a86d 100644 --- a/TealangLexer.l4 +++ b/TealangLexer.l4 @@ -39,7 +39,9 @@ BUILTINFUNC | KECCAK256 | SHA512 | ED25519 - | MULW + | LEN + | ITOB + | BTOI ; LET : 'let' ; @@ -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]+ ; diff --git a/TealangParser.g4 b/TealangParser.g4 index 9e9d965..69c1ebc 100644 --- a/TealangParser.g4 +++ b/TealangParser.g4 @@ -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 @@ -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 diff --git a/compiler/ast.go b/compiler/ast.go index e123772..46462ab 100644 --- a/compiler/ast.go +++ b/compiler/ast.go @@ -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 { @@ -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) } @@ -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) } @@ -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) } @@ -207,6 +186,7 @@ var builtinFun = map[string]bool{ "len": true, "itob": true, "btoi": true, + "mulw": true, } // TreeNodeIf represents a node in AST @@ -265,6 +245,14 @@ type assignNode struct { value ExprNodeIf } +type assignMulwNode struct { + *TreeNode + low string + high string + exprType exprType + value ExprNodeIf +} + type varDeclNode struct { *TreeNode name string @@ -272,6 +260,14 @@ type varDeclNode struct { value ExprNodeIf } +type varDeclMulwNode struct { + *TreeNode + low string + high string + exprType exprType + value ExprNodeIf +} + type constNode struct { *TreeNode name string @@ -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) @@ -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) @@ -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) } diff --git a/compiler/ast_test.go b/compiler/ast_test.go index 37c8d35..e42be7a 100644 --- a/compiler/ast_test.go +++ b/compiler/ast_test.go @@ -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'`) @@ -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) } diff --git a/compiler/codegen.go b/compiler/codegen.go index 8278d85..5d97972 100644 --- a/compiler/codegen.go +++ b/compiler/codegen.go @@ -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( @@ -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) diff --git a/compiler/codegen_test.go b/compiler/codegen_test.go index 28fd36b..42dc24d 100644 --- a/compiler/codegen_test.go +++ b/compiler/codegen_test.go @@ -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]) +} diff --git a/compiler/example_test.go b/compiler/example_test.go index 9a717f0..1728c82 100644 --- a/compiler/example_test.go +++ b/compiler/example_test.go @@ -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 } diff --git a/compiler/langspec.go b/compiler/langspec.go index 21901c4..4477063 100644 --- a/compiler/langspec.go +++ b/compiler/langspec.go @@ -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) { diff --git a/compiler/parser.go b/compiler/parser.go index 42672a0..4552612 100644 --- a/compiler/parser.go +++ b/compiler/parser.go @@ -293,6 +293,37 @@ func (l *treeNodeListener) EnterDeclareVar(ctx *gen.DeclareVarContext) { l.node = node } +func (l *treeNodeListener) EnterDeclareVarMulw(ctx *gen.DeclareVarMulwContext) { + identHigh := ctx.IDENT(0).GetText() + identLow := ctx.IDENT(1).GetText() + listener := newExprListener(l.ctx, l.parent) + ctx.MulwCall().EnterRule(listener) + exprNode := listener.getExpr() + + varType, err := exprNode.getType() + if err != nil { + reportError( + err.Error(), ctx.GetParser(), + ctx.MulwCall().(*gen.MulwCallContext).MULW().GetSymbol(), ctx.GetRuleContext(), + ) + return + } + + err = l.ctx.newVar(identLow, varType) + if err != nil { + reportError(err.Error(), ctx.GetParser(), ctx.IDENT(1).GetSymbol(), ctx.GetRuleContext()) + return + } + err = l.ctx.newVar(identHigh, varType) + if err != nil { + reportError(err.Error(), ctx.GetParser(), ctx.IDENT(0).GetSymbol(), ctx.GetRuleContext()) + return + } + + node := newVarDeclMulwNode(l.ctx, l.parent, identLow, identHigh, exprNode) + l.node = node +} + func (l *treeNodeListener) EnterDeclareNumberConst(ctx *gen.DeclareNumberConstContext) { varName := ctx.IDENT().GetText() varValue := ctx.NUMBER().GetText() @@ -411,20 +442,27 @@ func (l *treeNodeListener) EnterIfStatementFalse(ctx *gen.IfStatementFalseContex l.node = blockNode } -func (l *treeNodeListener) EnterAssign(ctx *gen.AssignContext) { - ident := ctx.IDENT().GetSymbol().GetText() - info, err := l.ctx.lookup(ident) +func getVarInfoForAssignment(ident string, ctx *context) (varInfo, error) { + info, err := ctx.lookup(ident) if err != nil { - reportError(err.Error(), ctx.GetParser(), ctx.IDENT().GetSymbol(), ctx.GetRuleContext()) - return + return varInfo{}, err } if info.constant { - reportError("cannot assign to a constant", ctx.GetParser(), ctx.IDENT().GetSymbol(), ctx.GetRuleContext()) - return + return varInfo{}, fmt.Errorf("cannot assign to a constant") } if info.function { - reportError("cannot assign to a function", ctx.GetParser(), ctx.IDENT().GetSymbol(), ctx.GetRuleContext()) + return varInfo{}, fmt.Errorf("cannot assign to a function") + } + + return info, nil +} + +func (l *treeNodeListener) EnterAssign(ctx *gen.AssignContext) { + ident := ctx.IDENT().GetSymbol().GetText() + info, err := getVarInfoForAssignment(ident, l.ctx) + if err != nil { + reportError(err.Error(), ctx.GetParser(), ctx.IDENT().GetSymbol(), ctx.GetRuleContext()) return } @@ -451,6 +489,51 @@ func (l *treeNodeListener) EnterAssign(ctx *gen.AssignContext) { l.node = node } +func (l *treeNodeListener) EnterAssignMulw(ctx *gen.AssignMulwContext) { + identHigh := ctx.IDENT(0).GetSymbol().GetText() + infoHigh, err := getVarInfoForAssignment(identHigh, l.ctx) + if err != nil { + reportError(err.Error(), ctx.GetParser(), ctx.IDENT(0).GetSymbol(), ctx.GetRuleContext()) + return + } + + identLow := ctx.IDENT(1).GetSymbol().GetText() + infoLow, err := getVarInfoForAssignment(identLow, l.ctx) + if err != nil { + reportError(err.Error(), ctx.GetParser(), ctx.IDENT(1).GetSymbol(), ctx.GetRuleContext()) + return + } + + node := newAssignMulwNode(l.ctx, l.parent, identLow, identHigh) + listener := newExprListener(l.ctx, node) + ctx.MulwCall().EnterRule(listener) + rhs := listener.getExpr() + node.value = rhs + rhsType, err := rhs.getType() + if err != nil { + reportError( + fmt.Sprintf("failed type resolution type: %s", err.Error()), + ctx.GetParser(), ctx.MulwCall().(*gen.MulwCallContext).MULW().GetSymbol(), ctx.GetRuleContext(), + ) + return + } + if infoHigh.theType != rhsType { + reportError( + fmt.Sprintf("incompatible types: (var) %s vs %s (expr)", infoHigh.theType, rhsType), + ctx.GetParser(), ctx.IDENT(0).GetSymbol(), ctx.GetRuleContext(), + ) + return + } + if infoLow.theType != rhsType { + reportError( + fmt.Sprintf("incompatible types: (var) %s vs %s (expr)", infoLow.theType, rhsType), + ctx.GetParser(), ctx.IDENT(1).GetSymbol(), ctx.GetRuleContext(), + ) + return + } + l.node = node +} + func (l *exprListener) EnterIdentifier(ctx *gen.IdentifierContext) { ident := ctx.IDENT().GetSymbol().GetText() variable, err := l.ctx.lookup(ident) @@ -658,6 +741,22 @@ func (l *exprListener) funCallEnterImpl(name string, allExpr []gen.IExprContext) return node } +func (l *exprListener) EnterMulwCall(ctx *gen.MulwCallContext) { + name := ctx.MULW().GetText() + exprNode := l.funCallEnterImpl(name, ctx.AllExpr()) + + err := exprNode.checkBuiltinArgs() + if err != nil { + parser := ctx.GetParser() + token := ctx.MULW().GetSymbol() + rule := ctx.GetRuleContext() + reportError(err.Error(), parser, token, rule) + return + } + + l.expr = exprNode +} + func (l *exprListener) EnterBuiltinObject(ctx *gen.BuiltinObjectContext) { listener := newExprListener(l.ctx, l.parent) ctx.BuiltinVarExpr().EnterRule(listener)