OSDN Git Service

add AST for if-else statement which contains lock or unlock statement (#32)
authoroysheng <33340252+oysheng@users.noreply.github.com>
Thu, 20 Dec 2018 10:24:21 +0000 (18:24 +0800)
committerPaladz <yzhu101@uottawa.ca>
Thu, 20 Dec 2018 10:24:21 +0000 (18:24 +0800)
* add expression for lock/unlock statement which included by if-else body

* add AST for if-else statement which contains lock or unlock statement

* delete redundant role

compiler/ast.go
compiler/checks.go
compiler/compile.go
compiler/environ.go

index 41f9e6a..fba7f21 100644 (file)
@@ -74,10 +74,43 @@ type Clause struct {
        // Values is the list of values unlocked or relocked in this clause.
        Values []ValueInfo `json:"values"`
 
+       // Conditions is the list of condition for if-else statements which body contains
+       // the lock or unlock statement in this clause.
+       Conditions map[string]Condition `json:"conditions"`
+
+       // CondValues is the map of values unlocked or relocked in this clause's
+       // if-else statements which body contains the lock or unlock statement.
+       CondValues map[string][]ValueInfo `json:"cond_values"`
+
        // Contracts is the list of contracts called by this clause.
        Contracts []string `json:"contracts,omitempty"`
 }
 
+// ValueInfo describes how a blockchain value is used in a contract clause.
+type ValueInfo struct {
+       // Name is the clause's name for this value.
+       Name string `json:"name"`
+
+       // Program is the program expression used to the lock the value, if
+       // the value is locked with "lock." If it's unlocked with "unlock"
+       // instead, this is empty.
+       Program string `json:"program,omitempty"`
+
+       // Asset is the expression describing the asset type the value must
+       // have, as it appears in a clause's "requires" section. If this is
+       // the contract value instead, this is empty.
+       Asset string `json:"asset,omitempty"`
+
+       // Amount is the expression describing the amount the value must
+       // have, as it appears in a clause's "requires" section. If this is
+       // the contract value instead, this is empty.
+       Amount string `json:"amount,omitempty"`
+
+       // Params is the list of parameters for amount expression. If the value
+       // of amount is a variable, this is empty.
+       Params []*Param `json:"params,omitempty"`
+}
+
 // HashCall describes a call to a hash function.
 type HashCall struct {
        // HashType is "sha3" or "sha256".
@@ -90,13 +123,21 @@ type HashCall struct {
        ArgType string `json:"arg_type"`
 }
 
-// IfBody describes a if ... else ... struct
-type IfStatmentBody struct {
-       // if statements body
-       trueBody []statement
+// Condition describes a condition expression.
+type Condition struct {
+       // Source is the string format of condition expression.
+       Source string `json:"source"`
 
-       // else statements body
-       falseBody []statement
+       // Params is the list of parameters for condition expression.
+       Params []*Param `json:"params,omitempty"`
+}
+
+// ContractArg is an argument with which to instantiate a contract as
+// a program. Exactly one of B, I, and S should be supplied.
+type ContractArg struct {
+       B *bool               `json:"boolean,omitempty"`
+       I *int64              `json:"integer,omitempty"`
+       S *chainjson.HexBytes `json:"string,omitempty"`
 }
 
 type statement interface {
@@ -121,6 +162,15 @@ func (s assignStatement) countVarRefs(counts map[string]int) {
        s.expr.countVarRefs(counts)
 }
 
+// IfStatmentBody describes the content of if-else structure
+type IfStatmentBody struct {
+       // if body statements
+       trueBody []statement
+
+       // else body statements
+       falseBody []statement
+}
+
 type ifStatement struct {
        condition expression
        body      *IfStatmentBody
@@ -265,15 +315,15 @@ func (v varRef) String() string {
        return string(v)
 }
 
-func (e varRef) typ(env *environ) typeDesc {
-       if entry := env.lookup(string(e)); entry != nil {
+func (v varRef) typ(env *environ) typeDesc {
+       if entry := env.lookup(string(v)); entry != nil {
                return entry.t
        }
        return nilType
 }
 
-func (e varRef) countVarRefs(counts map[string]int) {
-       counts[string(e)]++
+func (v varRef) countVarRefs(counts map[string]int) {
+       counts[string(v)]++
 }
 
 type bytesLiteral []byte
index a10fa92..2a4d663 100644 (file)
@@ -39,6 +39,74 @@ func checkStatRecursive(stmt statement, contractName string) bool {
        return false
 }
 
+func calClauseValues(contract *Contract, env *environ, stmt statement, conditions map[string]Condition, condValues map[string][]ValueInfo, index *int) (valueInfo *ValueInfo) {
+       switch s := stmt.(type) {
+       case *ifStatement:
+               *index++
+               strIndex := fmt.Sprintf("%d", *index)
+
+               conditionCounts := make(map[string]int)
+               s.condition.countVarRefs(conditionCounts)
+
+               params := []*Param{}
+               for v := range conditionCounts {
+                       if entry := env.lookup(v); entry != nil && (entry.r == roleContractParam || entry.r == roleContractValue || entry.r == roleClauseParam || entry.r == roleClauseVariable) {
+                               params = append(params, &Param{Name: v, Type: entry.t})
+                       }
+               }
+
+               condition := Condition{Source: s.condition.String(), Params: params}
+               conditions["condition_"+strIndex] = condition
+
+               trueValues := []ValueInfo{}
+               for _, trueStmt := range s.body.trueBody {
+                       trueValue := calClauseValues(contract, env, trueStmt, conditions, condValues, index)
+                       if trueValue != nil {
+                               trueValues = append(trueValues, *trueValue)
+                       }
+               }
+               condValues["truebody_"+strIndex] = trueValues
+
+               if len(s.body.falseBody) != 0 {
+                       falseValues := []ValueInfo{}
+                       for _, falseStmt := range s.body.falseBody {
+                               falseValue := calClauseValues(contract, env, falseStmt, conditions, condValues, index)
+                               if falseValue != nil {
+                                       falseValues = append(falseValues, *falseValue)
+                               }
+                       }
+                       condValues["falsebody_"+strIndex] = falseValues
+               }
+
+       case *lockStatement:
+               valueInfo = &ValueInfo{
+                       Amount:  s.lockedAmount.String(),
+                       Asset:   s.lockedAsset.String(),
+                       Program: s.program.String(),
+               }
+
+               lockCounts := make(map[string]int)
+               s.lockedAmount.countVarRefs(lockCounts)
+               if _, ok := lockCounts[s.lockedAmount.String()]; !ok {
+                       params := []*Param{}
+                       for v := range lockCounts {
+                               if entry := env.lookup(v); entry != nil && (entry.r == roleContractParam || entry.r == roleContractValue || entry.r == roleClauseParam || entry.r == roleClauseVariable) {
+                                       params = append(params, &Param{Name: v, Type: entry.t})
+                               }
+                       }
+                       valueInfo.Params = params
+               }
+
+       case *unlockStatement:
+               valueInfo = &ValueInfo{
+                       Amount: contract.Value.Amount,
+                       Asset:  contract.Value.Asset,
+               }
+       }
+
+       return valueInfo
+}
+
 func prohibitSigParams(contract *Contract) error {
        for _, p := range contract.Params {
                if p.Type == sigType {
index 4cb0fc5..778d138 100644 (file)
@@ -12,36 +12,6 @@ import (
        "github.com/bytom/protocol/vm/vmutil"
 )
 
-// ValueInfo describes how a blockchain value is used in a contract
-// clause.
-type ValueInfo struct {
-       // Name is the clause's name for this value.
-       Name string `json:"name"`
-
-       // Program is the program expression used to the lock the value, if
-       // the value is locked with "lock." If it's unlocked with "unlock"
-       // instead, this is empty.
-       Program string `json:"program,omitempty"`
-
-       // Asset is the expression describing the asset type the value must
-       // have, as it appears in a clause's "requires" section. If this is
-       // the contract value instead, this is empty.
-       Asset string `json:"asset,omitempty"`
-
-       // Amount is the expression describing the amount the value must
-       // have, as it appears in a clause's "requires" section. If this is
-       // the contract value instead, this is empty.
-       Amount string `json:"amount,omitempty"`
-}
-
-// ContractArg is an argument with which to instantiate a contract as
-// a program. Exactly one of B, I, and S should be supplied.
-type ContractArg struct {
-       B *bool               `json:"boolean,omitempty"`
-       I *int64              `json:"integer,omitempty"`
-       S *chainjson.HexBytes `json:"string,omitempty"`
-}
-
 // Compile parses a sequence of Equity contracts from the supplied reader
 // and produces Contract objects containing the compiled bytecode and
 // other analysis. If argMap is non-nil, it maps contract names to
@@ -84,26 +54,6 @@ func Compile(r io.Reader) ([]*Contract, error) {
                if err != nil {
                        return nil, errors.Wrap(err, "compiling contract")
                }
-               for _, clause := range contract.Clauses {
-                       for _, stmt := range clause.statements {
-                               switch s := stmt.(type) {
-                               case *lockStatement:
-                                       valueInfo := ValueInfo{
-                                               Amount:  s.lockedAmount.String(),
-                                               Asset:   s.lockedAsset.String(),
-                                               Program: s.program.String(),
-                                       }
-
-                                       clause.Values = append(clause.Values, valueInfo)
-                               case *unlockStatement:
-                                       valueInfo := ValueInfo{
-                                               Amount: contract.Value.Amount,
-                                               Asset:  contract.Value.Asset,
-                                       }
-                                       clause.Values = append(clause.Values, valueInfo)
-                               }
-                       }
-               }
        }
 
        return contracts, nil
@@ -329,6 +279,19 @@ func compileClause(b *builder, contractStk stack, contract *Contract, env *envir
                }
        }
 
+       conditions := make(map[string]Condition)
+       condValues := make(map[string][]ValueInfo)
+       index := 0
+       for _, stmt := range clause.statements {
+               valueInfo := calClauseValues(contract, env, stmt, conditions, condValues, &index)
+               if valueInfo != nil {
+                       clause.Values = append(clause.Values, *valueInfo)
+               } else {
+                       clause.Conditions = conditions
+                       clause.CondValues = condValues
+               }
+       }
+
        err = typeCheckClause(contract, clause, env)
        if err != nil {
                return err
index 6d770b1..ce3aff2 100644 (file)
@@ -24,7 +24,6 @@ const (
        roleContractValue
        roleClause
        roleClauseParam
-       roleClauseValue
        roleClauseVariable
 )
 
@@ -36,7 +35,6 @@ var roleDesc = map[role]string{
        roleContractValue:  "contract value",
        roleClause:         "clause",
        roleClauseParam:    "clause parameter",
-       roleClauseValue:    "clause value",
        roleClauseVariable: "clause variable",
 }