package service import ( "fmt" "go/ast" "go/parser" "go/token" "io" "strings" "sync" ) // Service specifies the api logic of transforming a source code format into another target format. type Service interface { Start() error SetHeaderWriter(io.Writer) error AddIncludeArduinoH(bool) } const ( // ErrorWorkerReaderIsNil ... ErrorWorkerReaderIsNil = "Reader should not be nil" // ErrorWorkerWriterIsNil ... ErrorWorkerWriterIsNil = "Writer should not be nil" FunctionGoHelperPrefix = "__GoHelper_" ) var ( funcDeclarations []string helpersForDeclarations []string dlock sync.Mutex ) // defaultService specifies the api logic of transforming a source code format into another target format. type defaultService struct { in io.Reader out io.Writer header io.Writer addIncludeArduinoH bool } // NewService creates a a new transpile and returns its address. func NewService(in io.Reader, out io.Writer) Service { return &defaultService{ in: in, out: out, } } func (s *defaultService) SetHeaderWriter(w io.Writer) error { if w == nil { return fmt.Errorf("Empty") } s.header = w return nil } func (s *defaultService) AddIncludeArduinoH(add bool) { s.addIncludeArduinoH = add } // Start ... func (s *defaultService) Start() error { if s.in == nil { return fmt.Errorf("Error: %s", ErrorWorkerReaderIsNil) } if s.out == nil { return fmt.Errorf("Error: %s", ErrorWorkerWriterIsNil) } // Read tokens from file by using Go's parser. fset := token.NewFileSet() file, err := parser.ParseFile(fset, "source.go", s.in, 0) if err != nil { return fmt.Errorf("ParseFile failed! %v", err) } // If source has no declarations then main it to an empty for loop. if file.Decls == nil { fmt.Fprint(s.out, "void loop() {}void setup() {}") return nil } // Use Goroutines to work concurrently. count := len(file.Decls) done := make(chan bool, count) dst := make([]chan string, count) for i := 0; i < count; i++ { dst[i] = make(chan string, 1) } funcDeclarations = nil helpersForDeclarations = nil // Start a transpile with an individual channel for each declaration in the source file. go func() { for i, decl := range file.Decls { handleDecl(i, decl, dst[i], done) } }() // Wait for all workers are done. for i := 0; i < count; i++ { select { case <-done: } } s.printIncludeHeaders() s.printFunctionDeclarations() s.printGoHelperDeclarations() // Print the ordered result. for i := 0; i < count; i++ { for content := range dst[i] { s.out.Write([]byte(content)) } } s.out.Write([]byte("\n")) // Print the AST. // ast.Fprint(os.Stderr, fset, file, nil) return nil } func (s *defaultService) printIncludeHeaders() { if !s.addIncludeArduinoH { return } h := "#include \n\n" s.out.Write([]byte(h)) } func (s *defaultService) printFunctionDeclarations() { dlock.Lock() defer dlock.Unlock() for _, f := range funcDeclarations { s.out.Write([]byte(f + "\n")) if s.header != nil { s.header.Write([]byte(f + "\n")) } } s.out.Write([]byte("\n")) } func (s *defaultService) printGoHelperDeclarations() { dlock.Lock() defer dlock.Unlock() for _, f := range helpersForDeclarations { helper := fmt.Sprintf(`void %v%v(void*) { %v(); vTaskDelete(NULL); }`, FunctionGoHelperPrefix, f, f) s.out.Write([]byte(helper + "\n")) } s.out.Write([]byte("\n")) } func addFunctionDeclaration(f string) { dlock.Lock() defer dlock.Unlock() funcDeclarations = append(funcDeclarations, f) } func handleBasicLit(bl *ast.BasicLit) string { return bl.Value } func handleUnaryExpr(expr *ast.UnaryExpr) string { code := expr.Op.String() code += handleExpr(expr.X) return code } func handleBinaryExpr(expr ast.Expr) string { be := expr.(*ast.BinaryExpr) code := handleExpr(be.X) code += be.Op.String() code += handleExpr(be.Y) return code } func handleCallExpr(expr *ast.CallExpr) string { code := handleExpr(expr.Fun) code += "(" args := make([]string, 0) for _, arg := range expr.Args { args = append(args, handleExpr(arg)) } code += strings.Join(args, ",") code += ")" return code } func handleDecl(id int, decl ast.Decl, dst chan<- string, done chan<- bool) { code := "" switch d := decl.(type) { case *ast.FuncDecl: code += handleFuncDecl(d) case *ast.GenDecl: code += handleGenDecl(d) } dst <- code close(dst) done <- true } func handleExpr(expr ast.Expr) string { code := "" switch e := expr.(type) { case *ast.BasicLit: code += handleBasicLit(e) case *ast.UnaryExpr: code += handleUnaryExpr(e) case *ast.BinaryExpr: code += handleBinaryExpr(e) case *ast.CallExpr: code += handleCallExpr(e) case *ast.Ident: code += handleIdent(e) case *ast.ParenExpr: code += handleParenExpr(e) case *ast.SelectorExpr: code += handleSelectorExpr(e) } return code } func handleParenExpr(stmt *ast.ParenExpr) string { code := "" code += handleExpr(stmt.X) return code } func handleFuncDecl(decl ast.Decl) string { fd := decl.(*ast.FuncDecl) if shouldSkipFunction(fd.Type) { return "" } code := "" name := "" ft := handleFuncDeclType(fd.Type) code += ft code += " " name = handleFuncDeclName(fd.Name) if name == "NewController" { return "" } code += name code += "(" fp := handleFuncDeclParams(fd.Type) code += fp addFunctionDeclaration(ft + " " + name + "(" + fp + ");") code += ") {" code += handleBlockStmt(fd.Body) code += "}" return code } func shouldSkipFunction(t *ast.FuncType) (res bool) { r := t.Results if r == nil { return } l := r.List if len(l) != 1 { return } p := l[0] if p == nil { return } pt := p.Type if pt == nil { return } i, ok := pt.(*ast.Ident) if !ok { return } if i.Name == "ShouldBeSkipped" { return true } return } func handleFuncDeclParams(t *ast.FuncType) string { code := "" if t.Params == nil || t.Params.List == nil { return code } values := make([]string, 0) for _, field := range t.Params.List { ftype := "" switch ft := field.Type.(type) { case *ast.Ident: ftype = handleIdent(ft) } for _, names := range field.Names { values = append(values, ftype+" "+names.Name) } } code += strings.Join(values, ",") return code } func handleFuncDeclName(ident *ast.Ident) string { code := "" if ident == nil { return code } code += ident.Name if val, ok := mapping[code]; ok { code = val } return code } func handleFuncDeclType(t *ast.FuncType) string { code := "" if t.Results == nil { return "void" } fl := t.Results if fl.NumFields() == 0 { panic("handleFuncDeclType: fl.NumFields() == 0") } switch ft := fl.List[0].Type.(type) { case *ast.Ident: code += handleIdent(ft) } return code } func handleGenDecl(decl ast.Decl) string { gd := decl.(*ast.GenDecl) code := "" switch gd.Tok { case token.CONST: code += "const " case token.VAR: code += "" } code += handleSpecs(gd.Specs) return code } func handleIdent(expr ast.Expr) string { ident := expr.(*ast.Ident) code := "" switch ident.Name { case "nil": code += "NULL" case "uint32": code += "unsigned long" case "uint64": code += "unsigned long long" case "string": code += "char*" default: code += ident.Name } return code } func handleSelectorExpr(expr ast.Expr) string { s := expr.(*ast.SelectorExpr) code := "" switch x := s.X.(type) { case *ast.Ident: code += handleIdent(x) } code += "." code += handleIdent(s.Sel) if val, ok := mapping[code]; ok { code = val } return code }