├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── go.mod ├── go.sum ├── main.go └── transpile ├── handlers ├── handleAssignStmt.go ├── handleAssignStmtExpr.go ├── handleBasicLit.go ├── handleBinaryExpr.go ├── handleBlockStmt.go ├── handleBranchStmt.go ├── handleCallExpr.go ├── handleCaseClause.go ├── handleDecl.go ├── handleDeclStmt.go ├── handleExpr.go ├── handleExprStmt.go ├── handleForStmt.go ├── handleFuncDecl.go ├── handleFuncDeclName.go ├── handleFuncDeclParams.go ├── handleFuncDeclType.go ├── handleGenDecl.go ├── handleIdent.go ├── handleIfStmt.go ├── handleImportSpec.go ├── handleParentExpr.go ├── handleSelectorExpr.go ├── handleSpecs.go ├── handleStmt.go ├── handleSwitchStmt.go ├── handleValueSpec.go ├── handleValueSpecNames.go ├── handleValueSpecType.go ├── handleValueSpecValues.go └── mapping.go ├── service.go └── service_test.go /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright 2022 Andreas Geiß 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TS=$(shell date -u '+%Y/%m/%d %H:%M:%S') 2 | 3 | all: install test 4 | 5 | install: 6 | @echo $(TS) Installing... 7 | @go get -u github.com/andygeiss/assert 8 | @go get -u github.com/andygeiss/esp32-controller 9 | @go get -u github.com/andygeiss/esp32-transpiler 10 | @echo $(TS) Done. 11 | 12 | test: 13 | @echo $(TS) Testing ... 14 | @go test -v ./... 15 | @echo $(TS) Done. 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ESP32 Transpiler 2 | 3 | [![License](https://img.shields.io/github/license/andygeiss/esp32)](https://github.com/andygeiss/esp32-transpiler/blob/master/LICENSE) 4 | [![Releases](https://img.shields.io/github/v/release/andygeiss/esp32)](https://github.com/andygeiss/esp32-transpiler/releases) 5 | [![Go Report Card](https://goreportcard.com/badge/github.com/andygeiss/esp32)](https://goreportcard.com/report/github.com/andygeiss/esp32) 6 | [![Maintainability](https://api.codeclimate.com/v1/badges/90bf72e5a7b538c9e50e/maintainability)](https://codeclimate.com/github/andygeiss/esp32-transpiler/maintainability) 7 | 8 | ## Purpose 9 | 10 | The [Arduino IDE](https://www.arduino.cc/en/Main/Software) is easy to use. 11 | But I faced problems like maintainability and testability at more complicated IoT projects. 12 | I needed to compile and flash the ESP32 before testing my code functionality by doing it 100% manually. 13 | 14 | This solution transpiles Golang into Arduino code, which can be compiled to an image by using the ESP32 toolchain. 15 | Now I am able to use a fully automated testing approach instead of doing it 100% manually. 16 | 17 | **Important**: 18 | 19 | The Transpiler only supports a small subset of the [Golang Language Specification](https://golang.org/ref/spec). 20 | Look at the [mapping](https://github.com/andygeiss/esp32-transpiler/blob/master/transpile/handlers/mapping.go) and the [tests](https://github.com/andygeiss/esp32-transpiler/blob/master/transpile/service_test.go) to get the current functionality. 21 | It is also not possible to trigger the C/C++ Garbage Collection, because Golang handles it automatically "under the hood". 22 | Go strings will be transpiled to C constant char arrays, which could be handled on the stack. 23 | 24 | ## Installation 25 | 26 | go get -u github.com/andygeiss/esp32-transpiler 27 | 28 | ## Usage 29 | 30 | Usage of esp32-transpiler: 31 | -source string 32 | Golang source file 33 | -target string 34 | Arduino sketch file 35 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/andygeiss/esp32-transpiler 2 | 3 | go 1.19 4 | 5 | require github.com/andygeiss/utils v0.9.1 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/andygeiss/utils v0.9.1 h1:PFFgmdwNDO2/bHA/4F7/fozifH+lPyuMBAhsBuTLGf8= 2 | github.com/andygeiss/utils v0.9.1/go.mod h1:Jatj6UsFNUMgf+9vPeMZ9il72nARlhMvc54r7AiPPiU= 3 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "github.com/andygeiss/esp32-transpiler/transpile" 7 | "os" 8 | ) 9 | 10 | func main() { 11 | source, target := getFlags() 12 | checkFlagsAreValid(source, target) 13 | safeTranspile(source, target) 14 | } 15 | 16 | func checkFlagsAreValid(source, target string) { 17 | if source == "" || target == "" { 18 | flag.Usage() 19 | os.Exit(1) 20 | } 21 | } 22 | 23 | func getFlags() (string, string) { 24 | source := flag.String("source", "", "Golang source file") 25 | target := flag.String("target", "", "Arduino sketch file") 26 | flag.Parse() 27 | return *source, *target 28 | } 29 | 30 | func printUsage() { 31 | fmt.Print("This program transpiles Golang source into corresponding Arduino sketches.\n\n") 32 | fmt.Print("Options:\n") 33 | flag.PrintDefaults() 34 | fmt.Print("\n") 35 | fmt.Print("Example:\n") 36 | fmt.Printf("\tesp32 -source impl/blink/controller.go -target impl/blink/controller.transpile\n\n") 37 | } 38 | 39 | func safeTranspile(source, target string) { 40 | // Read the Golang source file. 41 | in, err := os.Open(source) 42 | if err != nil { 43 | fmt.Fprintf(os.Stderr, "Go source file [%s] could not be opened! %v", source, err) 44 | os.Exit(1) 45 | } 46 | defer in.Close() 47 | // Create the Arduino sketch file. 48 | os.Remove(target) 49 | out, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR|os.O_SYNC, 0666) 50 | if err != nil { 51 | fmt.Fprintf(os.Stderr, "Arduino sketch file [%s] could not be opened! %v", target, err) 52 | os.Exit(1) 53 | } 54 | // Transpiles the Golang source into Arduino sketch. 55 | service := transpile.NewService(in, out) 56 | if err := service.Start(); err != nil { 57 | fmt.Fprintf(os.Stderr, "%v", err) 58 | os.Exit(1) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /transpile/handlers/handleAssignStmt.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "go/ast" 5 | ) 6 | 7 | func handleAssignStmt(as *ast.AssignStmt) string { 8 | code := handleAssignStmtExpr(as.Lhs) 9 | code += as.Tok.String() 10 | code += handleAssignStmtExpr(as.Rhs) 11 | return code 12 | } 13 | -------------------------------------------------------------------------------- /transpile/handlers/handleAssignStmtExpr.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "go/ast" 5 | "strings" 6 | ) 7 | 8 | func handleAssignStmtExpr(e []ast.Expr) string { 9 | ops := make([]string, 0) 10 | code := "" 11 | for _, op := range e { 12 | ops = append(ops, HandleExpr(op)) 13 | } 14 | code += strings.Join(ops, ",") 15 | return code 16 | } 17 | -------------------------------------------------------------------------------- /transpile/handlers/handleBasicLit.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import "go/ast" 4 | 5 | func handleBasicLit(bl *ast.BasicLit) string { 6 | return bl.Value 7 | } 8 | -------------------------------------------------------------------------------- /transpile/handlers/handleBinaryExpr.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import "go/ast" 4 | 5 | func handleBinaryExpr(expr ast.Expr) string { 6 | be := expr.(*ast.BinaryExpr) 7 | code := HandleExpr(be.X) 8 | code += be.Op.String() 9 | code += HandleExpr(be.Y) 10 | return code 11 | } 12 | -------------------------------------------------------------------------------- /transpile/handlers/handleBlockStmt.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import "go/ast" 4 | 5 | func handleBlockStmt(body *ast.BlockStmt) string { 6 | code := "" 7 | if body == nil { 8 | return code 9 | } 10 | for _, stmt := range body.List { 11 | code += handleStmt(stmt) 12 | } 13 | return code 14 | } 15 | -------------------------------------------------------------------------------- /transpile/handlers/handleBranchStmt.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | func handleBranchStmt() string { 4 | return "break;" 5 | } 6 | -------------------------------------------------------------------------------- /transpile/handlers/handleCallExpr.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "go/ast" 5 | "strings" 6 | ) 7 | 8 | func handleCallExpr(expr *ast.CallExpr) string { 9 | code := HandleExpr(expr.Fun) 10 | code += "(" 11 | args := make([]string, 0) 12 | for _, arg := range expr.Args { 13 | args = append(args, HandleExpr(arg)) 14 | } 15 | code += strings.Join(args, ",") 16 | code += ")" 17 | return code 18 | } 19 | -------------------------------------------------------------------------------- /transpile/handlers/handleCaseClause.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "go/ast" 5 | "strings" 6 | ) 7 | 8 | func handleCaseClause(cc *ast.CaseClause) string { 9 | code := "case " 10 | clauses := make([]string, 0) 11 | for _, clause := range cc.List { 12 | clauses = append(clauses, HandleExpr(clause)) 13 | } 14 | code += strings.Join(clauses, ",") 15 | code += ":" 16 | for _, body := range cc.Body { 17 | code += handleStmt(body) 18 | } 19 | return code 20 | } 21 | -------------------------------------------------------------------------------- /transpile/handlers/handleDecl.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import "go/ast" 4 | 5 | func HandleDecl(decl ast.Decl, dst chan<- string, done chan<- bool) { 6 | code := "" 7 | switch d := decl.(type) { 8 | case *ast.FuncDecl: 9 | code += handleFuncDecl(d) 10 | case *ast.GenDecl: 11 | code += handleGenDecl(d) 12 | } 13 | dst <- code 14 | close(dst) 15 | done <- true 16 | } 17 | -------------------------------------------------------------------------------- /transpile/handlers/handleDeclStmt.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import "go/ast" 4 | 5 | func handleDeclStmt(stmt *ast.DeclStmt) string { 6 | code := "" 7 | switch decl := stmt.Decl.(type) { 8 | case *ast.GenDecl: 9 | code += handleGenDecl(decl) 10 | } 11 | return code 12 | } 13 | -------------------------------------------------------------------------------- /transpile/handlers/handleExpr.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import "go/ast" 4 | 5 | func HandleExpr(expr ast.Expr) string { 6 | code := "" 7 | switch e := expr.(type) { 8 | case *ast.BasicLit: 9 | code += handleBasicLit(e) 10 | case *ast.BinaryExpr: 11 | code += handleBinaryExpr(e) 12 | case *ast.CallExpr: 13 | code += handleCallExpr(e) 14 | case *ast.Ident: 15 | code += handleIdent(e) 16 | case *ast.ParenExpr: 17 | code += handleParenExpr(e) 18 | case *ast.SelectorExpr: 19 | code += handleSelectorExpr(e) 20 | } 21 | return code 22 | } 23 | -------------------------------------------------------------------------------- /transpile/handlers/handleExprStmt.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import "go/ast" 4 | 5 | func handleExprStmt(stmt *ast.ExprStmt) string { 6 | code := "" 7 | switch x := stmt.X.(type) { 8 | case *ast.CallExpr: 9 | code += handleCallExpr(x) 10 | } 11 | return code 12 | } 13 | -------------------------------------------------------------------------------- /transpile/handlers/handleForStmt.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import "go/ast" 4 | 5 | func handleForStmt(stmt *ast.ForStmt) string { 6 | code := "" 7 | if stmt.Init == nil && stmt.Post == nil { 8 | code += "while" 9 | } else { 10 | code += "for" 11 | } 12 | code += "(" // stmt.Init 13 | code += handleBinaryExpr(stmt.Cond) // stmt.Cond 14 | code += "" // stmt.Post 15 | code += ") {" 16 | code += handleBlockStmt(stmt.Body) // stmt.Body 17 | code += "}" 18 | return code 19 | } 20 | -------------------------------------------------------------------------------- /transpile/handlers/handleFuncDecl.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import "go/ast" 4 | 5 | func handleFuncDecl(decl ast.Decl) string { 6 | fd := decl.(*ast.FuncDecl) 7 | code := "" 8 | name := "" 9 | code += handleFuncDeclType(fd.Type) 10 | code += " " 11 | name = handleFuncDeclName(fd.Name) 12 | if name == "NewController" { 13 | return "" 14 | } 15 | code += name 16 | code += "(" 17 | code += handleFuncDeclParams(fd.Type) 18 | code += ") {" 19 | code += handleBlockStmt(fd.Body) 20 | code += "}" 21 | return code 22 | } 23 | -------------------------------------------------------------------------------- /transpile/handlers/handleFuncDeclName.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import "go/ast" 4 | 5 | func handleFuncDeclName(ident *ast.Ident) string { 6 | code := "" 7 | if ident == nil { 8 | return code 9 | } 10 | code += ident.Name 11 | if val, ok := mapping[code]; ok { 12 | code = val 13 | } 14 | return code 15 | } 16 | -------------------------------------------------------------------------------- /transpile/handlers/handleFuncDeclParams.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "go/ast" 5 | "strings" 6 | ) 7 | 8 | func handleFuncDeclParams(t *ast.FuncType) string { 9 | code := "" 10 | if t.Params == nil || t.Params.List == nil { 11 | return code 12 | } 13 | values := make([]string, 0) 14 | for _, field := range t.Params.List { 15 | ftype := "" 16 | switch ft := field.Type.(type) { 17 | case *ast.Ident: 18 | ftype = handleIdent(ft) 19 | } 20 | for _, names := range field.Names { 21 | values = append(values, ftype+" "+names.Name) 22 | } 23 | } 24 | code += strings.Join(values, ",") 25 | return code 26 | } 27 | -------------------------------------------------------------------------------- /transpile/handlers/handleFuncDeclType.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import "go/ast" 4 | 5 | func handleFuncDeclType(t *ast.FuncType) string { 6 | code := "" 7 | if t.Results == nil { 8 | code = "void" 9 | } 10 | return code 11 | } 12 | -------------------------------------------------------------------------------- /transpile/handlers/handleGenDecl.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "go/ast" 5 | "go/token" 6 | ) 7 | 8 | func handleGenDecl(decl ast.Decl) string { 9 | gd := decl.(*ast.GenDecl) 10 | code := "" 11 | switch gd.Tok { 12 | case token.CONST: 13 | code += "const " 14 | case token.VAR: 15 | code += "" 16 | } 17 | code += handleSpecs(gd.Specs) 18 | return code 19 | } 20 | -------------------------------------------------------------------------------- /transpile/handlers/handleIdent.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import "go/ast" 4 | 5 | func handleIdent(expr ast.Expr) string { 6 | ident := expr.(*ast.Ident) 7 | code := "" 8 | switch ident.Name { 9 | case "string": 10 | code += "char*" 11 | default: 12 | code += ident.Name 13 | } 14 | return code 15 | } 16 | -------------------------------------------------------------------------------- /transpile/handlers/handleIfStmt.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "fmt" 5 | "go/ast" 6 | ) 7 | 8 | func handleIfStmt(stmt *ast.IfStmt) string { 9 | cond := HandleExpr(stmt.Cond) 10 | body := handleBlockStmt(stmt.Body) 11 | code := fmt.Sprintf(`if (%s) { %s }`, cond, body) 12 | if stmt.Else != nil { 13 | tail := handleBlockStmt(stmt.Else.(*ast.BlockStmt)) 14 | code += fmt.Sprintf(" else { %s }", tail) 15 | } 16 | return code 17 | } 18 | -------------------------------------------------------------------------------- /transpile/handlers/handleImportSpec.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import "go/ast" 4 | 5 | func handleImportSpec(spec ast.Spec) string { 6 | s := spec.(*ast.ImportSpec) 7 | code := "" 8 | if s.Name != nil { 9 | name := handleIdent(s.Name) 10 | if val, ok := mapping[name]; ok { 11 | name = val 12 | } 13 | if name != "" { 14 | if name != "controller" { 15 | code = "#include <" + name + ".h>\n" 16 | } 17 | } 18 | } 19 | return code 20 | } 21 | -------------------------------------------------------------------------------- /transpile/handlers/handleParentExpr.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import "go/ast" 4 | 5 | func handleParenExpr(stmt *ast.ParenExpr) string { 6 | code := "" 7 | code += HandleExpr(stmt.X) 8 | return code 9 | } 10 | -------------------------------------------------------------------------------- /transpile/handlers/handleSelectorExpr.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import "go/ast" 4 | 5 | func handleSelectorExpr(expr ast.Expr) string { 6 | s := expr.(*ast.SelectorExpr) 7 | code := "" 8 | switch x := s.X.(type) { 9 | case *ast.Ident: 10 | code += handleIdent(x) 11 | } 12 | code += "." 13 | code += handleIdent(s.Sel) 14 | if val, ok := mapping[code]; ok { 15 | code = val 16 | } 17 | return code 18 | } 19 | -------------------------------------------------------------------------------- /transpile/handlers/handleSpecs.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import "go/ast" 4 | 5 | func handleSpecs(specs []ast.Spec) string { 6 | code := "" 7 | for _, spec := range specs { 8 | switch spec.(type) { 9 | case *ast.ImportSpec: 10 | code += handleImportSpec(spec) 11 | case *ast.ValueSpec: 12 | code += handleValueSpec(spec) + ";" 13 | } 14 | } 15 | return code 16 | } 17 | -------------------------------------------------------------------------------- /transpile/handlers/handleStmt.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import "go/ast" 4 | 5 | func handleStmt(stmt ast.Stmt) string { 6 | code := "" 7 | switch s := stmt.(type) { 8 | case *ast.AssignStmt: 9 | code += handleAssignStmt(s) 10 | code += ";" 11 | case *ast.BranchStmt: 12 | code += handleBranchStmt() 13 | case *ast.CaseClause: 14 | code += handleCaseClause(s) 15 | case *ast.DeclStmt: 16 | code += handleDeclStmt(s) 17 | case *ast.ExprStmt: 18 | code += handleExprStmt(s) 19 | code += ";" 20 | case *ast.ForStmt: 21 | code += handleForStmt(s) 22 | case *ast.IfStmt: 23 | code += handleIfStmt(s) 24 | case *ast.SwitchStmt: 25 | code += handleSwitchStmt(s) 26 | } 27 | return code 28 | } 29 | -------------------------------------------------------------------------------- /transpile/handlers/handleSwitchStmt.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import "go/ast" 4 | 5 | func handleSwitchStmt(stmt *ast.SwitchStmt) string { 6 | code := "switch (" 7 | code += HandleExpr(stmt.Tag) 8 | code += "){" 9 | code += handleBlockStmt(stmt.Body) 10 | code += "}" 11 | return code 12 | } 13 | -------------------------------------------------------------------------------- /transpile/handlers/handleValueSpec.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import "go/ast" 4 | 5 | func handleValueSpec(spec ast.Spec) string { 6 | s := spec.(*ast.ValueSpec) 7 | code := "" 8 | code += handleValueSpecType(s.Type) 9 | code += " " 10 | code += handleValueSpecNames(s.Names) 11 | if s.Values != nil { 12 | code += " = " 13 | code += handleValueSpecValues(s.Values) 14 | } 15 | return code 16 | } 17 | -------------------------------------------------------------------------------- /transpile/handlers/handleValueSpecNames.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import "go/ast" 4 | 5 | func handleValueSpecNames(names []*ast.Ident) string { 6 | code := "" 7 | for _, name := range names { 8 | code += handleIdent(name) 9 | } 10 | return code 11 | } 12 | -------------------------------------------------------------------------------- /transpile/handlers/handleValueSpecType.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import "go/ast" 4 | 5 | func handleValueSpecType(expr ast.Expr) string { 6 | code := "" 7 | switch t := expr.(type) { 8 | case *ast.SelectorExpr: 9 | code += handleSelectorExpr(t) 10 | case *ast.Ident: 11 | code += handleIdent(t) 12 | } 13 | return code 14 | } 15 | -------------------------------------------------------------------------------- /transpile/handlers/handleValueSpecValues.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import "go/ast" 4 | 5 | func handleValueSpecValues(values []ast.Expr) string { 6 | code := "" 7 | for _, value := range values { 8 | switch v := value.(type) { 9 | case *ast.BasicLit: 10 | code += handleBasicLit(v) 11 | } 12 | } 13 | return code 14 | } 15 | -------------------------------------------------------------------------------- /transpile/handlers/mapping.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | var mapping = map[string]string{ 4 | "digital.Low": "LOW", 5 | "digital.High": "HIGH", 6 | "digital.ModeInput": "INPUT", 7 | "digital.ModeOutput": "OUTPUT", 8 | "digital.PinMode": "pinMode", 9 | "digital.Write": "digitalWrite", 10 | "random.Num": "random", 11 | "random.NumBetween": "random", 12 | "random.Seed": "randomSeed", 13 | "serial.Available": "Serial.available", 14 | "serial.BaudRate300": "300", 15 | "serial.BaudRate600": "600", 16 | "serial.BaudRate1200": "1200", 17 | "serial.BaudRate2400": "2400", 18 | "serial.BaudRate4800": "4800", 19 | "serial.BaudRate9600": "9600", 20 | "serial.BaudRate14400": "14400", 21 | "serial.BaudRate28800": "28800", 22 | "serial.BaudRate38400": "38400", 23 | "serial.BaudRate57600": "57600", 24 | "serial.BaudRate115200": "115200", 25 | "serial.Begin": "Serial.begin", 26 | "serial.Print": "Serial.print", 27 | "serial.Println": "Serial.println", 28 | "timer.Delay": "delay", 29 | "wifi": "WiFi", 30 | "wifi.Client": "WiFiClient", 31 | "client.Connect": "client.connect", 32 | "client.Println": "client.println", 33 | "client.Write": "client.write", 34 | "wifi.Begin": "WiFi.begin", 35 | "wifi.BeginEncrypted": "WiFi.begin", 36 | "wifi.BSSID": "WiFi.BSSID", 37 | "wifi.Disconnect": "WiFi.disconnect", 38 | "wifi.EncryptionType": "WiFi.encryptionType", 39 | "wifi.EncryptionTypeAuto": "8", 40 | "wifi.EncryptionTypeCCMP": "4", 41 | "wifi.EncryptionTypeNone": "7", 42 | "wifi.EncryptionTypeTKIP": "2", 43 | "wifi.EncryptionTypeWEP": "5", 44 | "wifi.LocalIP": "WiFi.localIP", 45 | "wifi.RSSI": "WiFi.RSSI", 46 | "wifi.ScanNetworks": "WiFi.scanNetworks", 47 | "wifi.SetDNS": "WiFi.setDNS", 48 | "wifi.SSID": "WiFi.SSID", 49 | "wifi.Status": "WiFi.status", 50 | "wifi.StatusConnected": "WL_CONNECTED", 51 | "wifi.StatusConnectionLost": "WL_CONNECTION_LOST", 52 | "wifi.StatusConnectFailed": "WL_CONNECT_FAILED", 53 | "wifi.StatusDisconnected": "WL_DISCONNECTED", 54 | "wifi.StatusIdle": "WL_IDLE_STATUS", 55 | "wifi.StatusNoShield": "WL_NO_SHIELD", 56 | "wifi.StatusNoSSIDAvailable": "WL_NO_SSID_AVAIL", 57 | "wifi.StatusScanCompleted": "WL_SCAN_COMPLETED", 58 | "Loop": "void loop", 59 | "Setup": "void setup", 60 | } 61 | -------------------------------------------------------------------------------- /transpile/service.go: -------------------------------------------------------------------------------- 1 | package transpile 2 | 3 | import ( 4 | "fmt" 5 | "github.com/andygeiss/esp32-transpiler/transpile/handlers" 6 | "go/parser" 7 | "go/token" 8 | "io" 9 | ) 10 | 11 | // Service specifies the api logic of transforming a source code format into another target format. 12 | type Service interface { 13 | Start() error 14 | } 15 | 16 | const ( 17 | // ErrorWorkerReaderIsNil ... 18 | ErrorWorkerReaderIsNil = "Reader should not be nil" 19 | // ErrorWorkerWriterIsNil ... 20 | ErrorWorkerWriterIsNil = "Writer should not be nil" 21 | ) 22 | 23 | // defaultService specifies the api logic of transforming a source code format into another target format. 24 | type defaultService struct { 25 | in io.Reader 26 | out io.Writer 27 | } 28 | 29 | // NewService creates a a new transpile and returns its address. 30 | func NewService(in io.Reader, out io.Writer) Service { 31 | return &defaultService{ 32 | in: in, 33 | out: out, 34 | } 35 | } 36 | 37 | // Start ... 38 | func (s *defaultService) Start() error { 39 | if s.in == nil { 40 | return fmt.Errorf("Error: %s", ErrorWorkerReaderIsNil) 41 | } 42 | if s.out == nil { 43 | return fmt.Errorf("Error: %s", ErrorWorkerWriterIsNil) 44 | } 45 | // Read tokens from file by using Go's parser. 46 | fs := token.NewFileSet() 47 | file, err := parser.ParseFile(fs, "source.go", s.in, 0) 48 | if err != nil { 49 | return fmt.Errorf("ParseFile failed! %v", err) 50 | } 51 | // If source has no declarations then main it to an empty for loop. 52 | if file.Decls == nil { 53 | _, _ = fmt.Fprint(s.out, "void loop() {} void setup() {}") 54 | return nil 55 | } 56 | // Use Goroutines to work concurrently. 57 | count := len(file.Decls) 58 | done := make(chan bool, count) 59 | dst := make([]chan string, count) 60 | for i := 0; i < count; i++ { 61 | dst[i] = make(chan string, 1) 62 | } 63 | // Start a transpile with an individual channel for each declaration in the source file. 64 | for i, decl := range file.Decls { 65 | go handlers.HandleDecl(decl, dst[i], done) 66 | } 67 | // Wait for all workers are done. 68 | for i := 0; i < count; i++ { 69 | select { 70 | case <-done: 71 | } 72 | } 73 | // Print the ordered result. 74 | for i := 0; i < count; i++ { 75 | for content := range dst[i] { 76 | _, _ = s.out.Write([]byte(content)) 77 | } 78 | } 79 | return nil 80 | } 81 | -------------------------------------------------------------------------------- /transpile/service_test.go: -------------------------------------------------------------------------------- 1 | package transpile_test 2 | 3 | import ( 4 | "bytes" 5 | "github.com/andygeiss/esp32-transpiler/transpile" 6 | "github.com/andygeiss/utils/assert" 7 | "strings" 8 | "testing" 9 | ) 10 | 11 | // Trim removes all the whitespaces and returns a new string. 12 | func Trim(s string) string { 13 | s = strings.Replace(s, " ", "", -1) 14 | s = strings.Replace(s, "\n", "", -1) 15 | s = strings.Replace(s, "\r", "", -1) 16 | s = strings.Replace(s, "\t", "", -1) 17 | return s 18 | } 19 | 20 | // Validate the content of a given source with an expected outcome by using a string compare. 21 | // The defaultService will be started and used to transform the source into an Arduino sketch format. 22 | func Validate(source, expected string, t *testing.T) { 23 | var in, out bytes.Buffer 24 | in.WriteString(source) 25 | service := transpile.NewService(&in, &out) 26 | _ = service.Start() 27 | got := out.String() 28 | tg, te := Trim(got), Trim(expected) 29 | assert.That("validation failed", t, tg, te) 30 | } 31 | 32 | func Test_Empty_Package(t *testing.T) { 33 | source := `package test` 34 | expected := `void loop(){} 35 | void setup() {} ` 36 | Validate(source, expected, t) 37 | } 38 | 39 | func Test_Function_Declaration(t *testing.T) { 40 | source := `package test 41 | func foo() {} 42 | func bar() {} 43 | ` 44 | expected := `void foo(){} 45 | void bar() {} ` 46 | Validate(source, expected, t) 47 | } 48 | 49 | func Test_Function_Declaration_With_Args(t *testing.T) { 50 | source := `package test 51 | func foo(x int) {} 52 | func bar(y int) {} 53 | ` 54 | expected := `void foo(int x){} 55 | void bar(int y) {} ` 56 | Validate(source, expected, t) 57 | } 58 | 59 | func Test_Const_String_Declaration(t *testing.T) { 60 | source := `package test 61 | const foo string = "bar" 62 | ` 63 | expected := ` 64 | const char* foo = "bar"; 65 | ` 66 | Validate(source, expected, t) 67 | } 68 | 69 | func Test_Var_String_Declaration(t *testing.T) { 70 | source := `package test 71 | var client wifi.Client 72 | ` 73 | expected := ` 74 | WiFiClient client; 75 | ` 76 | Validate(source, expected, t) 77 | } 78 | 79 | func Test_Function_With_Const_String_Declaration(t *testing.T) { 80 | source := `package test 81 | func foo() { 82 | const foo string = "bar" 83 | } 84 | ` 85 | expected := ` 86 | void foo() { 87 | const char* foo = "bar"; 88 | } 89 | ` 90 | Validate(source, expected, t) 91 | } 92 | func Test_Function_With_Var_String_Declaration(t *testing.T) { 93 | source := `package test 94 | func foo() { 95 | var foo string = "bar" 96 | } 97 | ` 98 | expected := ` 99 | void foo() { 100 | char* foo = "bar"; 101 | } 102 | ` 103 | Validate(source, expected, t) 104 | } 105 | func Test_Function_With_Function_Call(t *testing.T) { 106 | source := `package test 107 | func foo() { 108 | bar() 109 | } 110 | ` 111 | expected := ` 112 | void foo() { 113 | bar(); 114 | } 115 | ` 116 | Validate(source, expected, t) 117 | } 118 | func Test_Function_With_Function_Call_With_Args(t *testing.T) { 119 | source := `package test 120 | func foo() { 121 | bar(1,2,3) 122 | } 123 | ` 124 | expected := ` 125 | void foo() { 126 | bar(1,2,3); 127 | } 128 | ` 129 | Validate(source, expected, t) 130 | } 131 | func Test_Function_With_Function_Call_With_String(t *testing.T) { 132 | source := `package test 133 | func foo() { 134 | bar("foo") 135 | } 136 | ` 137 | expected := ` 138 | void foo() { 139 | bar("foo"); 140 | } 141 | ` 142 | Validate(source, expected, t) 143 | } 144 | 145 | func Test_Function_With_Package_Function_Call(t *testing.T) { 146 | source := `package test 147 | func foo() { 148 | foo.Bar(1,"2") 149 | } 150 | ` 151 | expected := ` 152 | void foo() { 153 | foo.Bar(1,"2"); 154 | } 155 | ` 156 | Validate(source, expected, t) 157 | } 158 | func Test_Function_With_Assignments(t *testing.T) { 159 | source := `package test 160 | func foo() { 161 | x = 1 162 | y = 2 163 | z = x + y 164 | } 165 | ` 166 | expected := ` 167 | void foo() { 168 | x = 1; 169 | y = 2; 170 | z = x + y; 171 | } 172 | ` 173 | Validate(source, expected, t) 174 | } 175 | 176 | func Test_Function_With_Package_Selector_Assignments(t *testing.T) { 177 | source := `package test 178 | func foo() { 179 | x = bar() 180 | y = pkg.Bar() 181 | z = x + y 182 | } 183 | ` 184 | expected := ` 185 | void foo() { 186 | x = bar(); 187 | y = pkg.Bar(); 188 | z = x + y; 189 | } 190 | ` 191 | Validate(source, expected, t) 192 | } 193 | 194 | func Test_Function_Ident_Mapping(t *testing.T) { 195 | source := `package test 196 | func foo() { 197 | serial.Begin() 198 | } 199 | ` 200 | expected := ` 201 | void foo() { 202 | Serial.begin(); 203 | } 204 | ` 205 | Validate(source, expected, t) 206 | } 207 | func Test_Function_With_Ident_Param(t *testing.T) { 208 | source := `package test 209 | func foo() { 210 | foo.Bar(1,"2",digital.Low) 211 | } 212 | ` 213 | expected := ` 214 | void foo() { 215 | foo.Bar(1,"2",LOW); 216 | } 217 | ` 218 | Validate(source, expected, t) 219 | } 220 | 221 | func Test_Function_With_Function_Param(t *testing.T) { 222 | source := `package test 223 | func foo() { 224 | serial.Println(wifi.LocalIP()) 225 | } 226 | ` 227 | expected := ` 228 | void foo() { 229 | Serial.println(WiFi.localIP()); 230 | } 231 | ` 232 | Validate(source, expected, t) 233 | } 234 | 235 | func Test_Package_Import(t *testing.T) { 236 | source := `package test 237 | import "github.com/andygeiss/esp32-mqtt/api/controller" 238 | import "github.com/andygeiss/esp32-mqtt/api/controller/serial" 239 | import "github.com/andygeiss/esp32/api/controller/timer" 240 | import wifi "github.com/andygeiss/esp32/api/controller/wifi" 241 | ` 242 | expected := ` 243 | #include 244 | ` 245 | Validate(source, expected, t) 246 | } 247 | 248 | func Test_Package_Import_But_Ignore_Controller(t *testing.T) { 249 | source := `package test 250 | import controller "github.com/andygeiss/esp32-controller" 251 | import "github.com/andygeiss/esp32-mqtt/api/controller/serial" 252 | import "github.com/andygeiss/esp32/api/controller/timer" 253 | import wifi "github.com/andygeiss/esp32/api/controller/wifi" 254 | ` 255 | expected := ` 256 | #include 257 | ` 258 | Validate(source, expected, t) 259 | } 260 | 261 | func Test_IfStmt_With_Condition_BasicLit_And_BasicLit(t *testing.T) { 262 | source := `package test 263 | func Setup() error {} 264 | func Loop() error { 265 | if 1 == 1 { 266 | serial.Println("1") 267 | } 268 | } 269 | ` 270 | expected := ` 271 | void setup() {} 272 | void loop() { 273 | if (1 == 1) { 274 | Serial.println("1"); 275 | } 276 | } 277 | ` 278 | Validate(source, expected, t) 279 | } 280 | 281 | func Test_IfStmt_With_Condition_Ident_And_BasicLit(t *testing.T) { 282 | source := `package test 283 | func Setup() error {} 284 | func Loop() error { 285 | if x == 1 { 286 | serial.Println("1") 287 | } 288 | } 289 | ` 290 | expected := ` 291 | void setup() {} 292 | void loop() { 293 | if (x == 1) { 294 | Serial.println("1"); 295 | } 296 | } 297 | ` 298 | Validate(source, expected, t) 299 | } 300 | 301 | func Test_IfStmt_With_Condition_CallExpr_And_BasicLit(t *testing.T) { 302 | source := `package test 303 | func Setup() error {} 304 | func Loop() error { 305 | if x() == 1 { 306 | serial.Println("1") 307 | } 308 | } 309 | ` 310 | expected := ` 311 | void setup() {} 312 | void loop() { 313 | if (x() == 1) { 314 | Serial.println("1"); 315 | } 316 | } 317 | ` 318 | Validate(source, expected, t) 319 | } 320 | 321 | func Test_IfStmt_With_Condition_Const_And_BasicLit(t *testing.T) { 322 | source := `package test 323 | const maxX = 1 324 | func Setup() error {} 325 | func Loop() error { 326 | if x == maxX { 327 | serial.Println("1") 328 | } 329 | } 330 | ` 331 | expected := ` 332 | const maxX = 1; 333 | void setup() {} 334 | void loop() { 335 | if (x == maxX) { 336 | Serial.println("1"); 337 | } 338 | } 339 | ` 340 | Validate(source, expected, t) 341 | } 342 | 343 | func Test_IfStmt_With_Else(t *testing.T) { 344 | source := `package test 345 | const maxX = 1 346 | func Setup() error {} 347 | func Loop() error { 348 | if x == maxX { 349 | serial.Println("1") 350 | } else { 351 | serial.Println("2") 352 | } 353 | } 354 | ` 355 | expected := ` 356 | const maxX = 1; 357 | void setup() {} 358 | void loop() { 359 | if (x == maxX) { 360 | Serial.println("1"); 361 | } else { 362 | Serial.println("2"); 363 | } 364 | } 365 | ` 366 | Validate(source, expected, t) 367 | } 368 | 369 | func Test_SwitchStmt_With_Ident_And_BasicLit(t *testing.T) { 370 | source := `package test 371 | func Setup() error {} 372 | func Loop() error { 373 | switch x { 374 | case 1: 375 | serial.Println("1") 376 | } 377 | } 378 | ` 379 | expected := ` 380 | void setup() {} 381 | void loop() { 382 | switch (x) { 383 | case 1: 384 | Serial.println("1"); 385 | } 386 | } 387 | ` 388 | Validate(source, expected, t) 389 | } 390 | 391 | func Test_SwitchStmt_With_Break(t *testing.T) { 392 | source := `package test 393 | func Setup() error {} 394 | func Loop() error { 395 | switch x { 396 | case 1: 397 | serial.Println("1") 398 | break 399 | case 2: 400 | serial.Println("1") 401 | } 402 | } 403 | ` 404 | expected := ` 405 | void setup() {} 406 | void loop() { 407 | switch (x) { 408 | case 1: 409 | Serial.println("1"); 410 | break; 411 | case 2: 412 | Serial.println("1"); 413 | } 414 | } 415 | ` 416 | Validate(source, expected, t) 417 | } 418 | 419 | func Test_ForLoop_WithoutInit_And_Post_Transpiles_To_While(t *testing.T) { 420 | source := `package test 421 | import wifi "github.com/andygeiss/esp32/api/controller/wifi" 422 | func Setup() error { 423 | serial.Begin(serial.BaudRate115200) 424 | wifi.BeginEncrypted("SSID", "PASS") 425 | for wifi.Status() != wifi.StatusConnected { 426 | serial.Println("Connecting ...") 427 | } 428 | serial.Println("Connected!") 429 | return nil 430 | } 431 | func Loop() error {} 432 | ` 433 | expected := ` 434 | #include 435 | void setup() { 436 | Serial.begin(115200); 437 | WiFi.begin("SSID","PASS"); 438 | while(WiFi.status()!=WL_CONNECTED){ 439 | Serial.println("Connecting..."); 440 | } 441 | Serial.println("Connected!"); 442 | } 443 | void loop() {} 444 | ` 445 | Validate(source, expected, t) 446 | } 447 | 448 | func Test_WiFiWebClient(t *testing.T) { 449 | source := `package test 450 | import wifi "github.com/andygeiss/esp32/api/controller/wifi" 451 | var client wifi.Client 452 | func Setup() error {} 453 | func Loop() error { 454 | serial.Print("Connecting to ") 455 | serial.Println(host) 456 | serial.Print(" ...") 457 | if (client.Connect(host, 443) == true) { 458 | serial.Println(" Connected!") 459 | } else { 460 | serial.Println(" Failed!") 461 | } 462 | } 463 | ` 464 | expected := `#include 465 | WiFiClient client; 466 | voidsetup(){} 467 | voidloop(){ 468 | Serial.print("Connecting to"); 469 | Serial.println(host); 470 | Serial.print(" ..."); 471 | if(client.connect(host, 443) == true){ 472 | Serial.println(" Connected!"); 473 | } else { 474 | Serial.println(" Failed!"); 475 | } 476 | }` 477 | Validate(source, expected, t) 478 | } 479 | --------------------------------------------------------------------------------