Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
304 changes: 304 additions & 0 deletions boardgen/boardgen.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,304 @@
package boardgen

import (
"fmt"
"os"
"path/filepath"
"slices"
"strings"
"text/template"

"github.com/tinygo-org/tinygo/goenv"
)

type Board struct {
Name string `json:"name,omitempty"`
BoardFiles map[string]string `json:"files,omitempty"`
Imports []string `json:"imports,omitempty"`
InitFuncs []string `json:"init-funcs,omitempty"`
PinAliases []PinAlias `json:"pin-aliases,omitempty"`
Constants []Variable `json:"constants,omitempty"`
Variables []Variable `json:"variables,omitempty"`
fileDirs []string `json:"-"`
tempDirs []string `json:"-"`
}

func (board *Board) Files() map[string]string {
if board == nil || board.BoardFiles == nil {
return nil
}
return board.BoardFiles
}

func (board *Board) DirsFromFiles() []string {
if board == nil || board.BoardFiles == nil {
return []string{}
}

for dst, src := range board.BoardFiles {
dir := filepath.Clean(dst)
if !isDir(src) {
dir = filepath.Dir(dir)
}
if !slices.Contains(board.fileDirs, dir) {
board.fileDirs = append(board.fileDirs, dir+"/")
}
}
return board.fileDirs
}

func (board *Board) TempDirs() []string {
if board == nil || board.tempDirs == nil {
return []string{}
}
return board.tempDirs
}

type PinAlias struct {
Name string `json:"name,omitempty"`
Type string `json:"type,omitempty"`
Pin string `json:"pin,omitempty"`
}

func (pa PinAlias) NameLen() int {
return len(pa.Name)
}

func (pa PinAlias) TypeLen() int {
return len(pa.Type)
}

func (pa PinAlias) Print(nameLen int, typeLen int) string {
if typeLen != 0 {
// Add an extra space between the name and type
nameLen++
}
pattern := "\t%-*s%-*s = %s"
return fmt.Sprintf(pattern, nameLen, pa.Name, typeLen, pa.Type, pa.Pin)
}

type Variable struct {
Name string `json:"name,omitempty"`
Type string `json:"type,omitempty"`
Quote bool `json:"quote,omitempty"`
Value string `json:"value,omitempty"`
}

func (v Variable) NameLen() int {
return len(v.Name)
}

func (v Variable) TypeLen() int {
return len(v.Type)
}

func (v Variable) Print(nameLen int, typeLen int) string {
if typeLen != 0 {
// Add an extra space between the name and type
nameLen++
}
pattern := "\t%-*s%-*s = %s"
if v.Quote {
pattern = "\t%-*s%-*s = %q"
}
return fmt.Sprintf(pattern, nameLen, v.Name, typeLen, v.Type, v.Value)
}

type boardData struct {
BuildTags []string `json:"build-tags,omitempty"`
Board *Board `json:"board,omitempty"`
}

const boardTemplate = `// Code generated by board-generator. DO NOT EDIT.
{{ buildtags .BuildTags -}}

package machine

{{ imports .Board.Imports -}}

// Pin aliases for the {{.Board.Name}}.
{{ pinAliases .Board.PinAliases -}}

{{ constants .Board.Constants -}}

{{ variables .Board.Variables -}}

{{ initFuncs .Board.InitFuncs -}}
`

var funcMap = template.FuncMap{
"buildtags": func(tags []string) string {
if len(tags) == 0 {
return "\n"
}
return "//go:build " + strings.Join(tags, " && ") + "\n\n"
},
"constants": func(constants []Variable) string {
if len(constants) == 0 {
return ""
}

longName, longType := longestVar[Variable](constants)
var builder strings.Builder

builder.WriteString("\nconst (\n")
for _, v := range constants {
if v.Name == "" || v.Value == "" {
// Can't declare a constant with no value
builder.WriteString(fmt.Sprintf("// Invalid constant: %+v\n", v))
continue
}
builder.WriteString(v.Print(longName, longType))
builder.WriteString("\n")
}
builder.WriteString(")\n")
return builder.String()
},
"imports": func(imports []string) string {
if len(imports) == 0 {
return ""
}
var builder strings.Builder
builder.WriteString("\nimport (\n")
for _, imp := range imports {
builder.WriteString(fmt.Sprintf("\t\"%s\"\n", imp))
}
builder.WriteString(")\n")
return builder.String()
},
"initFuncs": func(funcs []string) string {
if len(funcs) == 0 {
return ""
}
var builder strings.Builder
builder.WriteString("\nfunc init() {\n")
for _, fn := range funcs {
builder.WriteString(fmt.Sprintf("\t%s\n", fn))
}
builder.WriteString("}\n")
return builder.String()
},
"pinAliases": func(pinAliases []PinAlias) string {
if len(pinAliases) == 0 {
return ""
}

longName, longType := longestVar[PinAlias](pinAliases)
var builder strings.Builder

builder.WriteString("\nconst (\n")
for _, pa := range pinAliases {
if pa.Name == "" || pa.Pin == "" {
// Can't declare a pin alias with no pin or name
builder.WriteString(fmt.Sprintf("// Invalid pin alias: %+v\n", pa))
continue
}
builder.WriteString(pa.Print(longName, longType))
builder.WriteString("\n")
}
builder.WriteString(")\n")
return builder.String()
},
"variables": func(variables []Variable) string {
if len(variables) == 0 {
return ""
}

longName, longType := longestVar[Variable](variables)
var builder strings.Builder

builder.WriteString("\nvar (\n")

for _, v := range variables {
if v.Name == "" || v.Type == "" && v.Value == "" {
// Can't declare a variable with no name or no type/value
builder.WriteString(fmt.Sprintf("// Invalid variable: %+v\n", v))
continue
}
builder.WriteString(v.Print(longName, longType))
builder.WriteString("\n")
}
builder.WriteString(")\n")
return builder.String()
},
}

type varStruct interface {
NameLen() int
TypeLen() int
}

func longestVar[T varStruct](v []T) (int, int) {
longName := 0
longType := 0
for _, variable := range v {
if variable.NameLen() > longName {
longName = variable.NameLen()
}
if variable.TypeLen() > longType {
longType = variable.TypeLen()
}
}
return longName, longType
}

func (board *Board) Generate(target string, buildTags []string) error {
if board == nil {
return fmt.Errorf("board is nil")
}

board.BoardFiles = make(map[string]string)

// Make a temp directory to import the target's generated files for later import
err := os.MkdirAll(goenv.Get("GOCACHE"), 0777)
if err != nil {
return err
}
tmpDir, err := os.MkdirTemp(goenv.Get("GOCACHE"), target+".tmp")
if err != nil {
return err
}
board.tempDirs = append(board.tempDirs, tmpDir)

fileName := "boardgen_" + target + ".go"
destPath := filepath.Join("machine", fileName)
sourcePath := filepath.Join(tmpDir, fileName)

// Generate a go file for the board
boardData := boardData{
BuildTags: buildTags,
Board: board,
}
tmpl, err := template.New("board").Funcs(funcMap).Parse(boardTemplate)
if err != nil {
return err
}

fp, err := os.Create(sourcePath)
if err != nil {
return err
}
defer fp.Close()

err = tmpl.Execute(fp, boardData)
if err != nil {
return err
}
// Write the file to disk ASAP
err = fp.Sync()
if err != nil {
return err
}

board.BoardFiles[destPath] = sourcePath

return nil
}

func isDir(path string) bool {
f, err := os.Stat(path)
if err != nil {
return false
}
return f.IsDir()
}
Loading
Loading