package service import ( "fmt" "go/ast" "go/parser" "go/token" "io" "strings" "sync" "github.com/davecgh/go-spew/spew" ) // 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 addGoHelperDeclaration(f string) { dlock.Lock() defer dlock.Unlock() helpersForDeclarations = append(helpersForDeclarations, f) } func handleAssignStmt(as *ast.AssignStmt) string { code := handleAssignStmtExpr(as.Lhs) code += as.Tok.String() code += handleAssignStmtExpr(as.Rhs) return code } func handleIncDecStmt(as *ast.IncDecStmt) string { code := handleExpr(as.X) code += as.Tok.String() return code } func handleAssignStmtExpr(e []ast.Expr) string { ops := make([]string, 0) code := "" for _, op := range e { ops = append(ops, handleExpr(op)) } code += strings.Join(ops, ",") return code } 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 handleDeclStmt(stmt *ast.DeclStmt) string { code := "" switch decl := stmt.Decl.(type) { case *ast.GenDecl: code += handleGenDecl(decl) } return code } func handleGoStmt(stmt *ast.GoStmt) string { code := "" switch f := stmt.Call.Fun.(type) { case *ast.Ident: go_helper := FunctionGoHelperPrefix + f.Name addGoHelperDeclaration(f.Name) code += `xTaskCreate(` + go_helper + `,"",1024,NULL,1,NULL);` + "\n" } return code } 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 handleExprStmt(stmt *ast.ExprStmt) string { code := "" switch x := stmt.X.(type) { case *ast.CallExpr: code += handleCallExpr(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 handleBlockStmt(body *ast.BlockStmt) string { code := "" if body == nil { return code } for _, stmt := range body.List { code += handleStmt(stmt, false) } return code } func handleBranchStmt(stmt *ast.BranchStmt) string { return stmt.Tok.String() + ";" } func handleCaseClause(cc *ast.CaseClause) string { code := "case " clauses := make([]string, 0) for _, clause := range cc.List { clauses = append(clauses, handleExpr(clause)) } code += strings.Join(clauses, ",") code += ":" for _, body := range cc.Body { code += handleStmt(body, false) } 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 handleIfStmt(stmt *ast.IfStmt) string { cond := handleExpr(stmt.Cond) body := handleBlockStmt(stmt.Body) code := fmt.Sprintf(`if (%s) { %s }`, cond, body) if stmt.Else != nil { tail := handleBlockStmt(stmt.Else.(*ast.BlockStmt)) code += fmt.Sprintf(" else { %s }", tail) } return code } func handleImportSpec(spec ast.Spec) string { s := spec.(*ast.ImportSpec) code := "" if s.Name != nil { name := handleIdent(s.Name) if val, ok := mapping[name]; ok { name = val } if name != "" { if name != "controller" { code = "#include <" + name + ".h>\n" } } } 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 } func handleSpecs(specs []ast.Spec) string { code := "" for _, spec := range specs { switch spec.(type) { case *ast.ImportSpec: code += handleImportSpec(spec) case *ast.ValueSpec: code += handleValueSpec(spec) + ";" case *ast.TypeSpec: code += handleTypeSpec(spec) } } return code } func handleStmt(stmt ast.Stmt, standaloneAssignment bool) string { code := "" switch s := stmt.(type) { case *ast.AssignStmt: code += handleAssignStmt(s) if !standaloneAssignment { code += ";" } case *ast.IncDecStmt: code += handleIncDecStmt(s) if !standaloneAssignment { code += ";" } case *ast.BranchStmt: code += handleBranchStmt(s) case *ast.CaseClause: code += handleCaseClause(s) case *ast.DeclStmt: code += handleDeclStmt(s) case *ast.GoStmt: code += handleGoStmt(s) case *ast.ExprStmt: code += handleExprStmt(s) code += ";" case *ast.ForStmt: code += handleForStmt(s) case *ast.IfStmt: code += handleIfStmt(s) case *ast.SwitchStmt: code += handleSwitchStmt(s) case *ast.ReturnStmt: code += handleReturnStmt(s) default: spew.Dump(stmt) panic("handleStmt: unknown type") } return code } func handleForStmt(stmt *ast.ForStmt) string { code := "" is_while := false if stmt.Init == nil && stmt.Post == nil { is_while = true code += "while" } else { code += "for" } code += "(" if !is_while { code += handleStmt(stmt.Init, true) code += ";" } if stmt.Cond != nil { code += handleBinaryExpr(stmt.Cond) } else { code += "1" } if !is_while { code += ";" code += handleStmt(stmt.Post, true) } code += ") {" code += handleBlockStmt(stmt.Body) code += "}" return code } func handleSwitchStmt(stmt *ast.SwitchStmt) string { code := "switch (" code += handleExpr(stmt.Tag) code += "){" code += handleBlockStmt(stmt.Body) code += "}" return code } func handleReturnStmt(stmt *ast.ReturnStmt) string { code := "return " if len(stmt.Results) > 0 { code += handleExpr(stmt.Results[0]) } code += ";" return code }