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 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 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 = handleIdentExpr(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 += handleIdentExpr(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 }