Skip to content

Instantly share code, notes, and snippets.

@matiasinsaurralde
Created May 16, 2018 02:56
Show Gist options
  • Save matiasinsaurralde/861ceece0716f84af1c7b1b0891f588a to your computer and use it in GitHub Desktop.
Save matiasinsaurralde/861ceece0716f84af1c7b1b0891f588a to your computer and use it in GitHub Desktop.
package generator
import (
"bytes"
"fmt"
"go/ast"
"html/template"
"regexp"
"strconv"
"strings"
)
const (
commentPrefix = "// "
delegatePrefix = "create_delegate"
delegateGoTemplate = `
func {{.MethodName}}({{.Recv}}) {{.Results}} {
output := {{.Call}}
return {{.Ret}}
}
`
delegateCTemplate = `
{{.Results}} {{.MethodName}}({{.Recv}}) {
{{.Call}}
return {{.Ret}};
}
`
)
var (
delegateExpr = regexp.MustCompile(`(.*)\s(.*)\s(.*)\((.*)\)\s?(.*)`)
)
// DelegateAnnotation contains parameters required for generating delegate code
type DelegateAnnotation struct {
AssemblyName string
TypeName string
MethodName string
*ast.FuncDecl
*Input
}
type delegateGoTemplateData struct {
Recv string
Results string
Call string
Ret string
*DelegateAnnotation
}
type delegateCTemplateData struct {
Results string
Recv string
Call string
Ret string
*DelegateAnnotation
}
// Render compiles the template using the available info.
// TODO: avoid dup code when matching types
func (d DelegateAnnotation) Render() string {
// Initialize the template data structures:
goTemplateData := delegateGoTemplateData{DelegateAnnotation: &d}
CTemplateData := delegateCTemplateData{DelegateAnnotation: &d}
// Build app the function parameters:
CParams := []string{}
params := []string{}
callParams := []string{}
for _, p := range d.FuncDecl.Type.Params.List {
paramName := p.Names[0].Name
t := fmt.Sprintf("%v", p.Type)
var goType string
var cType string
var cgoWrap string
switch t {
case "int":
cgoWrap = "C.int"
goType = "int"
cType = "int"
default:
log.Fatalf("Unsupported type '%s' in function '%s'\n", t, d.Name.Name)
}
param := fmt.Sprintf("%s %s", paramName, goType)
params = append(params, param)
CParam := fmt.Sprintf("%s %s", cType, paramName)
CParams = append(CParams, CParam)
// Append the cgo wrapper if it's available:
if cgoWrap != "" {
callParams = append(callParams, fmt.Sprintf("%s(%s)", cgoWrap, paramName))
}
}
goTemplateData.Recv = strings.Join(params, ", ")
CTemplateData.Recv = strings.Join(CParams, ", ")
// Build the main cgo call:
cgoCall := bytes.NewBufferString("C.call(")
cgoCall.WriteString(strings.Join(callParams, ", "))
cgoCall.WriteString(")")
// TODO: handle named results, multiple results:
results := []string{}
CResults := []string{}
if d.FuncDecl.Type.Results != nil {
for _, p := range d.FuncDecl.Type.Results.List {
var resultName string
if len(p.Names) > 0 {
resultName = p.Names[0].Name
}
t := fmt.Sprintf("%v", p.Type)
var goType string
var cType string
switch t {
case "int":
goType = "int"
cType = "int"
default:
log.Fatalf("Unsupported type '%s' in function '%s'\n", t, d.Name.Name)
}
result := fmt.Sprintf("%s %s", resultName, goType)
results = append(results, result)
CResult := fmt.Sprintf("%s %s", resultName, cType)
CResults = append(CResults, CResult)
}
goTemplateData.Results = strings.TrimSpace(strings.Join(results, ", "))
CTemplateData.Results = strings.TrimSpace(strings.Join(CResults, ", "))
}
goTemplateData.Call = cgoCall.String()
// Build return statement:
goTemplateData.Ret = "output"
// Write glue code and bindings:
Ccall := bytes.Buffer{}
Ccall.WriteString(CResults[0])
delegateFuncName := fmt.Sprintf("createDelegate%s(%s, %s, %s)",
d.MethodName,
strconv.Quote(d.AssemblyName),
strconv.Quote(d.TypeName),
strconv.Quote(d.MethodName))
fmt.Println("*** ", Ccall.String())
fmt.Println("*** ", delegateFuncName)
/*
#include "binding.hpp"
int call(int s) {
int result = createDelegateHelloWorld("HelloWorld", "HelloWorld.HelloWorld", "Hello", 1, s);
return result;
}
*/
cOutput := bytes.Buffer{}
cTemplate := template.Must(template.New("delegate_c").Parse(delegateCTemplate))
err := cTemplate.Execute(&cOutput, CTemplateData)
if err != nil {
log.WithError(err).Fatal("Couldn't generate C code")
}
cOutput.WriteTo(d.glueCode)
// Render the function code:
// TODO: cache the templates
goOutput := bytes.Buffer{}
goTemplate := template.Must(template.New("delegate_go").Parse(delegateGoTemplate))
err = goTemplate.Execute(&goOutput, goTemplateData)
if err != nil {
log.WithError(err).Fatal("Couldn't generate Go code")
}
return goOutput.String()
}
// Annotation is an interface.
type Annotation interface {
Render() string
}
// TODO: implement error handling
func (i *Input) parseFuncAnnotation(s string, f *ast.FuncDecl) Annotation {
s = strings.TrimLeft(s, commentPrefix)
var annotation Annotation
if strings.HasPrefix(s, delegatePrefix) {
prefix := fmt.Sprintf("//%s:", delegatePrefix)
s = strings.TrimLeft(s, prefix)
s = strings.TrimSpace(s)
submatches := delegateExpr.FindAllStringSubmatch(s, -1)
matches := submatches[0]
annotation = &DelegateAnnotation{
AssemblyName: matches[1],
TypeName: matches[2],
MethodName: matches[3],
FuncDecl: f,
Input: i,
}
}
return annotation
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment