Skip to content

Instantly share code, notes, and snippets.

@apg
Created December 23, 2017 07:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save apg/87f05e6f224f1f7ffe2957ffdd04416b to your computer and use it in GitHub Desktop.
Save apg/87f05e6f224f1f7ffe2957ffdd04416b to your computer and use it in GitHub Desktop.
Prototype: using Go's ast tools to build a comment parsing contract engine.

Overview

$ go run law.go
package main

import "fmt"

// @require: not-empty: len(x) > 0
func Foo(x string) {
        if !(len(x) > 0) {
                panic("not-empty")
        }

        fmt.Printf("Hello, %s!\n", x)
}

func main() {
        Foo("")
}

Idea: go test supports -cover which will rewrite your source code, annotating it with line coverage statements that provide a way in which to report code coverage. A new tool that could augment the build and/or test tools for the purposes of enforcing contracts during debug, or testing would be really neat. Beyond this basic prototype, however, I'm not yet sure how to get there...

package main
import (
"fmt"
"go/ast"
"go/parser"
"go/printer"
"go/token"
"os"
"strings"
)
const Program = `
package main
import "fmt"
// @require: not-empty: len(x) > 0
func Foo(x string) {
fmt.Printf("Hello, %s!\n", x)
}
func main() {
Foo("")
}
`
func main() {
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "foo.go", []byte(Program), parser.ParseComments)
if err != nil {
fmt.Println(err)
return
}
for _, dec := range f.Decls {
if fdec, ok := dec.(*ast.FuncDecl); ok {
if fdec.Doc != nil {
for _, c := range fdec.Doc.List {
bits := strings.Split(c.Text, ":")
if len(bits) != 3 {
continue
}
if !strings.Contains(bits[0], "@require") {
continue
}
contractName := fmt.Sprintf("%q", strings.TrimSpace(bits[1]))
contractRawExpr := bits[2]
expr, err := parser.ParseExprFrom(fset, "foo.go", []byte(contractRawExpr), 0)
if err != nil {
fmt.Printf("Unable to parse contract expression: %s", err)
os.Exit(1)
}
ifs := &ast.IfStmt{
Cond: &ast.UnaryExpr{Op: token.NOT, X: expr},
Body: &ast.BlockStmt{
List: []ast.Stmt{
&ast.ExprStmt{
X: &ast.CallExpr{
Fun: ast.NewIdent("panic"),
Args: []ast.Expr{&ast.BasicLit{Kind: token.STRING, Value: contractName}},
},
},
},
},
}
newBody := []ast.Stmt{ifs}
fdec.Body.List = append(newBody, fdec.Body.List...)
}
}
}
}
printer.Fprint(os.Stdout, fset, f)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment