OSDN Git Service

fix StrLiteral and BytesLiteral to support constant (#11)
[bytom/equity.git] / compiler / compile.go
index e86399d..76913a2 100644 (file)
@@ -5,7 +5,6 @@ import (
        "fmt"
        "io"
        "io/ioutil"
-       "strings"
 
        chainjson "github.com/bytom/encoding/json"
        "github.com/bytom/errors"
@@ -304,7 +303,10 @@ func compileClause(b *builder, contractStk stack, contract *Contract, env *envir
                        return err
                }
        }
-       assignIndexes(clause)
+
+       if err = assignIndexes(clause); err != nil {
+               return err
+       }
 
        var stk stack
        for _, p := range clause.Params {
@@ -318,6 +320,10 @@ func compileClause(b *builder, contractStk stack, contract *Contract, env *envir
        // a count of the number of times each variable is referenced
        counts := make(map[string]int)
        for _, s := range clause.statements {
+               if stmt, ok := s.(*defineStatement); ok && stmt.expr == nil {
+                       continue
+               }
+
                s.countVarRefs(counts)
                if stmt, ok := s.(*ifStatement); ok {
                        for _, trueStmt := range stmt.body.trueBody {
@@ -395,6 +401,16 @@ func compileStatement(b *builder, stk stack, contract *Contract, env *environ, c
                                st.countVarRefs(counts)
                        }
 
+                       // modify value amount because of using only once
+                       if counts[contract.Value.Amount] > 1 {
+                               counts[contract.Value.Amount] = 1
+                       }
+
+                       // modify value asset because of using only once
+                       if counts[contract.Value.Asset] > 1 {
+                               counts[contract.Value.Asset] = 1
+                       }
+
                        for _, st := range stmt.body.trueBody {
                                if stk, err = compileStatement(b, stk, contract, env, clause, counts, st, sequence); err != nil {
                                        return stk, err
@@ -413,6 +429,16 @@ func compileStatement(b *builder, stk stack, contract *Contract, env *environ, c
                                st.countVarRefs(counts)
                        }
 
+                       // modify value amount because of using only once
+                       if counts[contract.Value.Amount] > 1 {
+                               counts[contract.Value.Amount] = 1
+                       }
+
+                       // modify value asset because of using only once
+                       if counts[contract.Value.Asset] > 1 {
+                               counts[contract.Value.Asset] = 1
+                       }
+
                        stk = condStk
                        b.addJump(stk, "endif_"+strSequence)
                        b.addJumpTarget(stk, "else_"+strSequence)
@@ -426,25 +452,74 @@ func compileStatement(b *builder, stk stack, contract *Contract, env *environ, c
                b.addJumpTarget(stk, "endif_"+strSequence)
 
        case *defineStatement:
+               // add environ for define variable
+               if err = env.add(stmt.variable.Name, stmt.variable.Type, roleClauseVariable); err != nil {
+                       return stk, err
+               }
+
+               // check whether the variable is used or not
+               if counts[stmt.variable.Name] == 0 {
+                       return stk, fmt.Errorf("the defined variable \"%s\" is unused in clause \"%s\"", stmt.variable.Name, clause.Name)
+               }
+
+               if stmt.expr != nil {
+                       // variable
+                       stk, err = compileExpr(b, stk, contract, clause, env, counts, stmt.expr)
+                       if err != nil {
+                               return stk, errors.Wrapf(err, "in define statement in clause \"%s\"", clause.Name)
+                       }
+
+                       // modify stack name
+                       stk.str = stmt.variable.Name
+               }
+
+       case *assignStatement:
+               // find variable from environ with roleClauseVariable
+               if entry := env.lookup(string(stmt.variable.Name)); entry != nil {
+                       if entry.r != roleClauseVariable {
+                               return stk, fmt.Errorf("the type of variable is not roleClauseVariable in assign statement in clause \"%s\"", clause.Name)
+                       }
+                       stmt.variable.Type = entry.t
+               } else {
+                       return stk, fmt.Errorf("the variable \"%s\" is not defined before the assign statement in clause \"%s\"", stmt.variable.Name, clause.Name)
+               }
+
+               // temporary store the counts of defined variable
+               varCount := counts[stmt.variable.Name]
+
+               // calculate the counts of variable for assign statement
+               tmpCounts := make(map[string]int)
+               stmt.countVarRefs(tmpCounts)
+
+               // modify the map counts of defined variable to 1 and minus the number of defined variable
+               // when the assign expression contains the defined variable
+               if tmpCounts[stmt.variable.Name] > 0 {
+                       counts[stmt.variable.Name] = 1
+                       varCount -= tmpCounts[stmt.variable.Name]
+               } else {
+                       depth := stk.find(stmt.variable.Name)
+                       switch depth {
+                       case 0:
+                               break
+                       case 1:
+                               stk = b.addSwap(stk)
+                       default:
+                               stk = b.addRoll(stk, depth)
+                       }
+                       stk = b.addDrop(stk)
+               }
+
                // variable
                stk, err = compileExpr(b, stk, contract, clause, env, counts, stmt.expr)
                if err != nil {
                        return stk, errors.Wrapf(err, "in define statement in clause \"%s\"", clause.Name)
                }
 
-               // check variable type
-               if stmt.expr.typ(env) != stmt.varName.Type {
-                       return stk, fmt.Errorf("expression in define statement in clause \"%s\" has type \"%s\", must be \"%s\"",
-                               clause.Name, stmt.expr.typ(env), stmt.varName.Type)
-               }
+               // restore the defined variable counts
+               counts[stmt.variable.Name] = varCount
 
                // modify stack name
-               stk.str = stmt.varName.Name
-
-               // add environ for define variable
-               if err = env.add(stmt.varName.Name, stmt.varName.Type, roleClauseVariable); err != nil {
-                       return stk, err
-               }
+               stk.str = stmt.variable.Name
 
        case *verifyStatement:
                stk, err = compileExpr(b, stk, contract, clause, env, counts, stmt.expr)
@@ -476,24 +551,42 @@ func compileStatement(b *builder, stk stack, contract *Contract, env *environ, c
                        stk = b.addAmount(stk, contract.Value.Amount)
                        stk = b.addAsset(stk, contract.Value.Asset)
                } else {
-                       if strings.Contains(stmt.lockedAmount.String(), contract.Value.Amount) {
-                               stk = b.addAmount(stk, contract.Value.Amount)
-                       }
-
-                       if strings.Contains(stmt.lockedAsset.String(), contract.Value.Asset) {
-                               stk = b.addAsset(stk, contract.Value.Asset)
-                       }
+                       // calculate the counts of variable for lockStatement
+                       lockCounts := make(map[string]int)
+                       stmt.countVarRefs(lockCounts)
 
                        // amount
-                       stk, err = compileExpr(b, stk, contract, clause, env, counts, stmt.lockedAmount)
-                       if err != nil {
-                               return stk, errors.Wrapf(err, "in lock statement in clause \"%s\"", clause.Name)
+                       switch {
+                       case stmt.lockedAmount.String() == contract.Value.Amount:
+                               stk = b.addAmount(stk, contract.Value.Amount)
+                       case stmt.lockedAmount.String() != contract.Value.Amount && lockCounts[contract.Value.Amount] > 0:
+                               stk = b.addAmount(stk, contract.Value.Amount)
+                               stk, err = compileExpr(b, stk, contract, clause, env, counts, stmt.lockedAmount)
+                               if err != nil {
+                                       return stk, errors.Wrapf(err, "in lock statement in clause \"%s\"", clause.Name)
+                               }
+                       default:
+                               stk, err = compileExpr(b, stk, contract, clause, env, counts, stmt.lockedAmount)
+                               if err != nil {
+                                       return stk, errors.Wrapf(err, "in lock statement in clause \"%s\"", clause.Name)
+                               }
                        }
 
                        // asset
-                       stk, err = compileExpr(b, stk, contract, clause, env, counts, stmt.lockedAsset)
-                       if err != nil {
-                               return stk, errors.Wrapf(err, "in lock statement in clause \"%s\"", clause.Name)
+                       switch {
+                       case stmt.lockedAsset.String() == contract.Value.Asset:
+                               stk = b.addAsset(stk, contract.Value.Asset)
+                       case stmt.lockedAsset.String() != contract.Value.Asset && lockCounts[contract.Value.Asset] > 0:
+                               stk = b.addAsset(stk, contract.Value.Asset)
+                               stk, err = compileExpr(b, stk, contract, clause, env, counts, stmt.lockedAsset)
+                               if err != nil {
+                                       return stk, errors.Wrapf(err, "in lock statement in clause \"%s\"", clause.Name)
+                               }
+                       default:
+                               stk, err = compileExpr(b, stk, contract, clause, env, counts, stmt.lockedAsset)
+                               if err != nil {
+                                       return stk, errors.Wrapf(err, "in lock statement in clause \"%s\"", clause.Name)
+                               }
                        }
                }