OSDN Git Service

Hulk did something
[bytom/vapor.git] / equity / compiler / parse.go
diff --git a/equity/compiler/parse.go b/equity/compiler/parse.go
new file mode 100644 (file)
index 0000000..956825e
--- /dev/null
@@ -0,0 +1,652 @@
+package compiler
+
+import (
+       "bytes"
+       "encoding/hex"
+       "fmt"
+       "strconv"
+       "unicode"
+)
+
+// We have some function naming conventions.
+//
+// For terminals:
+//   scanX     takes buf and position, returns new position (and maybe a value)
+//   peekX     takes *parser, returns bool or string
+//   consumeX  takes *parser and maybe a required literal, maybe returns value
+//             also updates the parser position
+//
+// For nonterminals:
+//   parseX    takes *parser, returns AST node, updates parser position
+
+type parser struct {
+       buf []byte
+       pos int
+}
+
+func (p *parser) errorf(format string, args ...interface{}) {
+       panic(parserErr{buf: p.buf, offset: p.pos, format: format, args: args})
+}
+
+// parse is the main entry point to the parser
+func parse(buf []byte) (contracts []*Contract, err error) {
+       defer func() {
+               if val := recover(); val != nil {
+                       if e, ok := val.(parserErr); ok {
+                               err = e
+                       } else {
+                               panic(val)
+                       }
+               }
+       }()
+       p := &parser{buf: buf}
+       contracts = parseContracts(p)
+       return
+}
+
+// parse functions
+
+func parseContracts(p *parser) []*Contract {
+       var result []*Contract
+       if pos := scanKeyword(p.buf, p.pos, "contract"); pos < 0 {
+               p.errorf("expected contract")
+       }
+
+       for peekKeyword(p) == "contract" {
+               contract := parseContract(p)
+               result = append(result, contract)
+       }
+       return result
+}
+
+// contract name(p1, p2: t1, p3: t2) locks value { ... }
+func parseContract(p *parser) *Contract {
+       consumeKeyword(p, "contract")
+       name := consumeIdentifier(p)
+       params := parseParams(p)
+       // locks amount of asset
+       consumeKeyword(p, "locks")
+       value := ValueInfo{}
+       value.Amount = consumeIdentifier(p)
+       consumeKeyword(p, "of")
+       value.Asset = consumeIdentifier(p)
+       consumeTok(p, "{")
+       clauses := parseClauses(p)
+       consumeTok(p, "}")
+       return &Contract{Name: name, Params: params, Clauses: clauses, Value: value}
+}
+
+// (p1, p2: t1, p3: t2)
+func parseParams(p *parser) []*Param {
+       var params []*Param
+       consumeTok(p, "(")
+       first := true
+       for !peekTok(p, ")") {
+               if first {
+                       first = false
+               } else {
+                       consumeTok(p, ",")
+               }
+               pt := parseParamsType(p)
+               params = append(params, pt...)
+       }
+       consumeTok(p, ")")
+       return params
+}
+
+func parseClauses(p *parser) []*Clause {
+       var clauses []*Clause
+       for !peekTok(p, "}") {
+               c := parseClause(p)
+               clauses = append(clauses, c)
+       }
+       return clauses
+}
+
+func parseParamsType(p *parser) []*Param {
+       firstName := consumeIdentifier(p)
+       params := []*Param{&Param{Name: firstName}}
+       for peekTok(p, ",") {
+               consumeTok(p, ",")
+               name := consumeIdentifier(p)
+               params = append(params, &Param{Name: name})
+       }
+       consumeTok(p, ":")
+       typ := consumeIdentifier(p)
+       for _, parm := range params {
+               if tdesc, ok := types[typ]; ok {
+                       parm.Type = tdesc
+               } else {
+                       p.errorf("unknown type %s", typ)
+               }
+       }
+       return params
+}
+
+func parseClause(p *parser) *Clause {
+       var c Clause
+       consumeKeyword(p, "clause")
+       c.Name = consumeIdentifier(p)
+       c.Params = parseParams(p)
+       consumeTok(p, "{")
+       c.statements = parseStatements(p)
+       consumeTok(p, "}")
+       return &c
+}
+
+func parseStatements(p *parser) []statement {
+       var statements []statement
+       for !peekTok(p, "}") {
+               s := parseStatement(p)
+               statements = append(statements, s)
+       }
+       return statements
+}
+
+func parseStatement(p *parser) statement {
+       switch peekKeyword(p) {
+       case "if":
+               return parseIfStmt(p)
+       case "define":
+               return parseDefineStmt(p)
+       case "assign":
+               return parseAssignStmt(p)
+       case "verify":
+               return parseVerifyStmt(p)
+       case "lock":
+               return parseLockStmt(p)
+       case "unlock":
+               return parseUnlockStmt(p)
+       }
+       panic(parseErr(p.buf, p.pos, "unknown keyword \"%s\"", peekKeyword(p)))
+}
+
+func parseIfStmt(p *parser) *ifStatement {
+       consumeKeyword(p, "if")
+       condition := parseExpr(p)
+       body := &IfStatmentBody{}
+       consumeTok(p, "{")
+       body.trueBody = parseStatements(p)
+       consumeTok(p, "}")
+       if peekKeyword(p) == "else" {
+               consumeKeyword(p, "else")
+               consumeTok(p, "{")
+               body.falseBody = parseStatements(p)
+               consumeTok(p, "}")
+       }
+       return &ifStatement{condition: condition, body: body}
+}
+
+func parseDefineStmt(p *parser) *defineStatement {
+       defineStat := &defineStatement{}
+       consumeKeyword(p, "define")
+       param := &Param{}
+       param.Name = consumeIdentifier(p)
+       consumeTok(p, ":")
+       variableType := consumeIdentifier(p)
+       if tdesc, ok := types[variableType]; ok {
+               param.Type = tdesc
+       } else {
+               p.errorf("unknown type %s", variableType)
+       }
+       defineStat.variable = param
+       if peekTok(p, "=") {
+               consumeTok(p, "=")
+               defineStat.expr = parseExpr(p)
+       }
+       return defineStat
+}
+
+func parseAssignStmt(p *parser) *assignStatement {
+       consumeKeyword(p, "assign")
+       varName := consumeIdentifier(p)
+       consumeTok(p, "=")
+       expr := parseExpr(p)
+       return &assignStatement{variable: &Param{Name: varName}, expr: expr}
+}
+
+func parseVerifyStmt(p *parser) *verifyStatement {
+       consumeKeyword(p, "verify")
+       expr := parseExpr(p)
+       return &verifyStatement{expr: expr}
+}
+
+func parseLockStmt(p *parser) *lockStatement {
+       consumeKeyword(p, "lock")
+       lockedAmount := parseExpr(p)
+       consumeKeyword(p, "of")
+       lockedAsset := parseExpr(p)
+       consumeKeyword(p, "with")
+       program := parseExpr(p)
+       return &lockStatement{lockedAmount: lockedAmount, lockedAsset: lockedAsset, program: program}
+}
+
+func parseUnlockStmt(p *parser) *unlockStatement {
+       consumeKeyword(p, "unlock")
+       unlockedAmount := parseExpr(p)
+       consumeKeyword(p, "of")
+       unlockedAsset := parseExpr(p)
+       return &unlockStatement{unlockedAmount: unlockedAmount, unlockedAsset: unlockedAsset}
+}
+
+func parseExpr(p *parser) expression {
+       // Uses the precedence-climbing algorithm
+       // <https://en.wikipedia.org/wiki/Operator-precedence_parser#Precedence_climbing_method>
+       expr := parseUnaryExpr(p)
+       expr2, pos := parseExprCont(p, expr, 0)
+       if pos < 0 {
+               p.errorf("expected expression")
+       }
+       p.pos = pos
+       return expr2
+}
+
+func parseUnaryExpr(p *parser) expression {
+       op, pos := scanUnaryOp(p.buf, p.pos)
+       if pos < 0 {
+               return parseExpr2(p)
+       }
+       p.pos = pos
+       expr := parseUnaryExpr(p)
+       return &unaryExpr{op: op, expr: expr}
+}
+
+func parseExprCont(p *parser, lhs expression, minPrecedence int) (expression, int) {
+       for {
+               op, pos := scanBinaryOp(p.buf, p.pos)
+               if pos < 0 || op.precedence < minPrecedence {
+                       break
+               }
+               p.pos = pos
+
+               rhs := parseUnaryExpr(p)
+
+               for {
+                       op2, pos2 := scanBinaryOp(p.buf, p.pos)
+                       if pos2 < 0 || op2.precedence <= op.precedence {
+                               break
+                       }
+                       rhs, p.pos = parseExprCont(p, rhs, op2.precedence)
+                       if p.pos < 0 {
+                               return nil, -1 // or is this an error?
+                       }
+               }
+               lhs = &binaryExpr{left: lhs, right: rhs, op: op}
+       }
+       return lhs, p.pos
+}
+
+func parseExpr2(p *parser) expression {
+       if expr, pos := scanLiteralExpr(p.buf, p.pos); pos >= 0 {
+               p.pos = pos
+               return expr
+       }
+       return parseExpr3(p)
+}
+
+func parseExpr3(p *parser) expression {
+       e := parseExpr4(p)
+       if peekTok(p, "(") {
+               args := parseArgs(p)
+               return &callExpr{fn: e, args: args}
+       }
+       return e
+}
+
+func parseExpr4(p *parser) expression {
+       if peekTok(p, "(") {
+               consumeTok(p, "(")
+               e := parseExpr(p)
+               consumeTok(p, ")")
+               return e
+       }
+       if peekTok(p, "[") {
+               var elts []expression
+               consumeTok(p, "[")
+               first := true
+               for !peekTok(p, "]") {
+                       if first {
+                               first = false
+                       } else {
+                               consumeTok(p, ",")
+                       }
+                       e := parseExpr(p)
+                       elts = append(elts, e)
+               }
+               consumeTok(p, "]")
+               return listExpr(elts)
+       }
+       name := consumeIdentifier(p)
+       return varRef(name)
+}
+
+func parseArgs(p *parser) []expression {
+       var exprs []expression
+       consumeTok(p, "(")
+       first := true
+       for !peekTok(p, ")") {
+               if first {
+                       first = false
+               } else {
+                       consumeTok(p, ",")
+               }
+               e := parseExpr(p)
+               exprs = append(exprs, e)
+       }
+       consumeTok(p, ")")
+       return exprs
+}
+
+// peek functions
+
+func peekKeyword(p *parser) string {
+       name, _ := scanIdentifier(p.buf, p.pos)
+       return name
+}
+
+func peekTok(p *parser, token string) bool {
+       pos := scanTok(p.buf, p.pos, token)
+       return pos >= 0
+}
+
+// consume functions
+
+var keywords = []string{
+       "contract", "clause", "verify", "locks", "of",
+       "lock", "with", "unlock", "if", "else",
+       "define", "assign", "true", "false",
+}
+
+func consumeKeyword(p *parser, keyword string) {
+       pos := scanKeyword(p.buf, p.pos, keyword)
+       if pos < 0 {
+               p.errorf("expected keyword %s", keyword)
+       }
+       p.pos = pos
+}
+
+func consumeIdentifier(p *parser) string {
+       name, pos := scanIdentifier(p.buf, p.pos)
+       if pos < 0 {
+               p.errorf("expected identifier")
+       }
+       p.pos = pos
+       return name
+}
+
+func consumeTok(p *parser, token string) {
+       pos := scanTok(p.buf, p.pos, token)
+       if pos < 0 {
+               p.errorf("expected %s token", token)
+       }
+       p.pos = pos
+}
+
+// scan functions
+
+func scanUnaryOp(buf []byte, offset int) (*unaryOp, int) {
+       // Maximum munch. Make sure "-3" scans as ("-3"), not ("-", "3").
+       if _, pos := scanIntLiteral(buf, offset); pos >= 0 {
+               return nil, -1
+       }
+       for _, op := range unaryOps {
+               newOffset := scanTok(buf, offset, op.op)
+               if newOffset >= 0 {
+                       return &op, newOffset
+               }
+       }
+       return nil, -1
+}
+
+func scanBinaryOp(buf []byte, offset int) (*binaryOp, int) {
+       offset = skipWsAndComments(buf, offset)
+       var (
+               found     *binaryOp
+               newOffset = -1
+       )
+       for i, op := range binaryOps {
+               offset2 := scanTok(buf, offset, op.op)
+               if offset2 >= 0 {
+                       if found == nil || len(op.op) > len(found.op) {
+                               found = &binaryOps[i]
+                               newOffset = offset2
+                       }
+               }
+       }
+       return found, newOffset
+}
+
+// TODO(bobg): boolean literals?
+func scanLiteralExpr(buf []byte, offset int) (expression, int) {
+       offset = skipWsAndComments(buf, offset)
+       intliteral, newOffset := scanIntLiteral(buf, offset)
+       if newOffset >= 0 {
+               return intliteral, newOffset
+       }
+       strliteral, newOffset := scanStrLiteral(buf, offset)
+       if newOffset >= 0 {
+               return strliteral, newOffset
+       }
+       bytesliteral, newOffset := scanBytesLiteral(buf, offset) // 0x6c249a...
+       if newOffset >= 0 {
+               return bytesliteral, newOffset
+       }
+       booleanLiteral, newOffset := scanBoolLiteral(buf, offset) // true or false
+       if newOffset >= 0 {
+               return booleanLiteral, newOffset
+       }
+       return nil, -1
+}
+
+func scanIdentifier(buf []byte, offset int) (string, int) {
+       offset = skipWsAndComments(buf, offset)
+       i := offset
+       for ; i < len(buf) && isIDChar(buf[i], i == offset); i++ {
+       }
+       if i == offset {
+               return "", -1
+       }
+       return string(buf[offset:i]), i
+}
+
+func scanTok(buf []byte, offset int, s string) int {
+       offset = skipWsAndComments(buf, offset)
+       prefix := []byte(s)
+       if bytes.HasPrefix(buf[offset:], prefix) {
+               return offset + len(prefix)
+       }
+       return -1
+}
+
+func scanKeyword(buf []byte, offset int, keyword string) int {
+       id, newOffset := scanIdentifier(buf, offset)
+       if newOffset < 0 {
+               return -1
+       }
+       if id != keyword {
+               return -1
+       }
+       return newOffset
+}
+
+func scanIntLiteral(buf []byte, offset int) (integerLiteral, int) {
+       offset = skipWsAndComments(buf, offset)
+       start := offset
+       if offset < len(buf) && buf[offset] == '-' {
+               offset++
+       }
+       i := offset
+       for ; i < len(buf) && unicode.IsDigit(rune(buf[i])); i++ {
+               // the literal is BytesLiteral when it starts with 0x/0X
+               if buf[i] == '0' && i < len(buf)-1 && (buf[i+1] == 'x' || buf[i+1] == 'X') {
+                       return 0, -1
+               }
+       }
+       if i > offset {
+               n, err := strconv.ParseInt(string(buf[start:i]), 10, 64)
+               if err != nil {
+                       return 0, -1
+               }
+               return integerLiteral(n), i
+       }
+       return 0, -1
+}
+
+func scanStrLiteral(buf []byte, offset int) (bytesLiteral, int) {
+       offset = skipWsAndComments(buf, offset)
+       if offset >= len(buf) || buf[offset] != '\'' {
+               return bytesLiteral{}, -1
+       }
+       var byteBuf bytesLiteral
+       for i := offset + 1; i < len(buf); i++ {
+               if buf[i] == '\'' {
+                       return byteBuf, i + 1
+               }
+               if buf[i] == '\\' && i < len(buf)-1 {
+                       if c, ok := scanEscape(buf[i+1]); ok {
+                               byteBuf = append(byteBuf, c)
+                               i++
+                               continue
+                       }
+               }
+               byteBuf = append(byteBuf, buf[i])
+       }
+       panic(parseErr(buf, offset, "unterminated string literal"))
+}
+
+func scanBytesLiteral(buf []byte, offset int) (bytesLiteral, int) {
+       offset = skipWsAndComments(buf, offset)
+       if offset+4 >= len(buf) {
+               return nil, -1
+       }
+       if buf[offset] != '0' || (buf[offset+1] != 'x' && buf[offset+1] != 'X') {
+               return nil, -1
+       }
+       if !isHexDigit(buf[offset+2]) || !isHexDigit(buf[offset+3]) {
+               return nil, -1
+       }
+       i := offset + 4
+       for ; i < len(buf); i += 2 {
+               if i == len(buf)-1 {
+                       panic(parseErr(buf, offset, "odd number of digits in hex literal"))
+               }
+               if !isHexDigit(buf[i]) {
+                       break
+               }
+               if !isHexDigit(buf[i+1]) {
+                       panic(parseErr(buf, offset, "odd number of digits in hex literal"))
+               }
+       }
+       decoded := make([]byte, hex.DecodedLen(i-(offset+2)))
+       _, err := hex.Decode(decoded, buf[offset+2:i])
+       if err != nil {
+               return bytesLiteral{}, -1
+       }
+       return bytesLiteral(decoded), i
+}
+
+func scanBoolLiteral(buf []byte, offset int) (booleanLiteral, int) {
+       offset = skipWsAndComments(buf, offset)
+       if offset >= len(buf) {
+               return false, -1
+       }
+
+       newOffset := scanKeyword(buf, offset, "true")
+       if newOffset < 0 {
+               if newOffset = scanKeyword(buf, offset, "false"); newOffset < 0 {
+                       return false, -1
+               }
+               return false, newOffset
+       }
+       return true, newOffset
+}
+
+func skipWsAndComments(buf []byte, offset int) int {
+       var inComment bool
+       for ; offset < len(buf); offset++ {
+               c := buf[offset]
+               if inComment {
+                       if c == '\n' {
+                               inComment = false
+                       }
+               } else {
+                       if c == '/' && offset < len(buf)-1 && buf[offset+1] == '/' {
+                               inComment = true
+                               offset++ // skip two chars instead of one
+                       } else if !unicode.IsSpace(rune(c)) {
+                               break
+                       }
+               }
+       }
+       return offset
+}
+
+func isHexDigit(b byte) bool {
+       return (b >= '0' && b <= '9') || (b >= 'a' && b <= 'f') || (b >= 'A' && b <= 'F')
+}
+
+func isIDChar(c byte, initial bool) bool {
+       if c >= 'a' && c <= 'z' {
+               return true
+       }
+       if c >= 'A' && c <= 'Z' {
+               return true
+       }
+       if c == '_' {
+               return true
+       }
+       if initial {
+               return false
+       }
+       return unicode.IsDigit(rune(c))
+}
+
+type parserErr struct {
+       buf    []byte
+       offset int
+       format string
+       args   []interface{}
+}
+
+func parseErr(buf []byte, offset int, format string, args ...interface{}) error {
+       return parserErr{buf: buf, offset: offset, format: format, args: args}
+}
+
+func (p parserErr) Error() string {
+       // Lines start at 1, columns start at 0, like nature intended.
+       line := 1
+       col := 0
+       for i := 0; i < p.offset; i++ {
+               if p.buf[i] == '\n' {
+                       line++
+                       col = 0
+               } else {
+                       col++
+               }
+       }
+       args := []interface{}{line, col}
+       args = append(args, p.args...)
+       return fmt.Sprintf("line %d, col %d: "+p.format, args...)
+}
+
+func scanEscape(c byte) (byte, bool) {
+       escapeFlag := true
+       switch c {
+       case '\'', '"', '\\':
+       case 'b':
+               c = '\b'
+       case 'f':
+               c = '\f'
+       case 'n':
+               c = '\n'
+       case 'r':
+               c = '\r'
+       case 't':
+               c = '\t'
+       case 'v':
+               c = '\v'
+       default:
+               escapeFlag = false
+       }
+       return c, escapeFlag
+}