--- /dev/null
+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
+}