feat(cli): Re-engineer CLIs, split play & compile

Play depends on bridge and compile on compiler package. Before, the compiler depended on bridge, but we could not use the compiler to build the library, as the bridge depends on the library. Also, play can now start having slightly more options e.g. wav out etc.
This commit is contained in:
Veikko Sariola
2020-12-18 14:18:00 +02:00
parent 2d00640e06
commit 7f049acf88
11 changed files with 513 additions and 365 deletions

View File

@ -12,70 +12,73 @@ import (
"github.com/vsariola/sointu"
)
//go:generate go run generate.go
type Compiler struct {
Template *template.Template
Amd64 bool
OS string
DisableSections bool
Template *template.Template
OS string
Arch string
}
// New returns a new compiler using the default .asm templates
func New() (*Compiler, error) {
func New(os string, arch string) (*Compiler, error) {
_, myname, _, _ := runtime.Caller(0)
templateDir := filepath.Join(path.Dir(myname), "..", "templates")
compiler, err := NewFromTemplates(templateDir)
compiler, err := NewFromTemplates(os, arch, templateDir)
return compiler, err
}
func NewFromTemplates(directory string) (*Compiler, error) {
globPtrn := filepath.Join(directory, "*.*")
func NewFromTemplates(os string, arch string, templateDirectory string) (*Compiler, error) {
globPtrn := filepath.Join(templateDirectory, "*.*")
tmpl, err := template.New("base").Funcs(sprig.TxtFuncMap()).ParseGlob(globPtrn)
if err != nil {
return nil, fmt.Errorf(`could not create template based on directory "%v": %v`, directory, err)
return nil, fmt.Errorf(`could not create template based on directory "%v": %v`, templateDirectory, err)
}
return &Compiler{Template: tmpl, Amd64: runtime.GOARCH == "amd64", OS: runtime.GOOS}, nil
}
func (com *Compiler) compile(templateName string, data interface{}) (string, error) {
result := bytes.NewBufferString("")
err := com.Template.ExecuteTemplate(result, templateName, data)
return result.String(), err
return &Compiler{Template: tmpl, OS: os, Arch: arch}, nil
}
func (com *Compiler) Library() (map[string]string, error) {
if com.Arch != "386" && com.Arch != "amd64" {
return nil, fmt.Errorf(`compiling as a library is supported only on 386 and amd64 architectures (targeted architecture was %v)`, com.Arch)
}
templates := []string{"library.asm", "library.h"}
features := AllFeatures{}
m := NewMacros(*com, features)
m.Library = true
asmCode, err := com.compile("library.asm", m)
if err != nil {
return nil, fmt.Errorf(`could not execute template "library.asm": %v`, err)
retmap := map[string]string{}
for _, templateName := range templates {
macros := NewMacros(*com, features)
macros.Library = true
populatedTemplate, extension, err := com.compile(templateName, macros)
if err != nil {
return nil, fmt.Errorf(`could not execute template "%v": %v`, templateName, err)
}
retmap[extension] = populatedTemplate
}
m = NewMacros(*com, features)
m.Library = true
header, err := com.compile("library.h", &m)
if err != nil {
return nil, fmt.Errorf(`could not execute template "library.h": %v`, err)
}
return map[string]string{"asm": asmCode, "h": header}, nil
return retmap, nil
}
func (com *Compiler) Player(song *sointu.Song, maxSamples int) (map[string]string, error) {
func (com *Compiler) Song(song *sointu.Song) (map[string]string, error) {
if com.Arch != "386" && com.Arch != "amd64" {
return nil, fmt.Errorf(`compiling a song player is supported only on 386 and amd64 architectures (targeted architecture was %v)`, com.Arch)
}
templates := []string{"player.asm", "player.h"}
features := NecessaryFeaturesFor(song.Patch)
retmap := map[string]string{}
encodedPatch, err := Encode(&song.Patch, features)
if err != nil {
return nil, fmt.Errorf(`could not encode patch: %v`, err)
}
asmCode, err := com.compile("player.asm", NewPlayerMacros(*com, features, song, encodedPatch, maxSamples))
if err != nil {
return nil, fmt.Errorf(`could not execute template "player.asm": %v`, err)
for _, templateName := range templates {
macros := NewPlayerMacros(*com, features, song, encodedPatch)
populatedTemplate, extension, err := com.compile(templateName, macros)
if err != nil {
return nil, fmt.Errorf(`could not execute template "%v": %v`, templateName, err)
}
retmap[extension] = populatedTemplate
}
header, err := com.compile("player.h", NewPlayerMacros(*com, features, song, encodedPatch, maxSamples))
if err != nil {
return nil, fmt.Errorf(`could not execute template "player.h": %v`, err)
}
return map[string]string{"asm": asmCode, "h": header}, nil
return retmap, nil
}
func (com *Compiler) compile(templateName string, data interface{}) (string, string, error) {
result := bytes.NewBufferString("")
err := com.Template.ExecuteTemplate(result, templateName, data)
extension := filepath.Ext(templateName)
return result.String(), extension, err
}

View File

@ -1,61 +0,0 @@
// The following directive is necessary to make the package coherent:
// +build ignore
// This program generates the library headers and assembly files for the library
package main
import (
"flag"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"runtime"
"github.com/vsariola/sointu/compiler"
)
func main() {
targetArch := flag.String("arch", runtime.GOARCH, "Target architecture. Defaults to Go architecture. Possible values: amd64, 386 (anything else is assumed 386)")
targetOs := flag.String("os", runtime.GOOS, "Target OS. Defaults to current Go OS. Possible values: windows, darwin, linux (anything else is assumed linux)")
flag.Usage = printUsage
flag.Parse()
if flag.NArg() != 1 {
flag.Usage()
os.Exit(0)
}
comp, err := compiler.New()
if err != nil {
fmt.Fprintf(os.Stderr, `error creating compiler: %v`, err)
os.Exit(1)
}
comp.Amd64 = *targetArch == "amd64"
comp.OS = *targetOs
library, err := comp.Library()
if err != nil {
fmt.Fprintf(os.Stderr, `error compiling library: %v`, err)
os.Exit(1)
}
filenames := map[string]string{"h": "sointu.h", "asm": "sointu.asm"}
for t, contents := range library {
filename := filenames[t]
err := ioutil.WriteFile(filepath.Join(flag.Args()[0], filename), []byte(contents), os.ModePerm)
if err != nil {
fmt.Fprintf(os.Stderr, `could not write to file "%v": %v`, filename, err)
os.Exit(1)
}
}
os.Exit(0)
}
func printUsage() {
fmt.Fprintf(os.Stderr, "Sointu command line utility for generating the library .asm and .h files.\nUsage: %s [flags] outputDirectory\n", os.Args[0])
flag.PrintDefaults()
}

View File

@ -14,21 +14,23 @@ type OplistEntry struct {
}
type Macros struct {
Stacklocs []string
Output16Bit bool
Clip bool
Library bool
Sine int // TODO: how can we elegantly access global constants in template, without wrapping each one by one
Trisaw int
Pulse int
Gate int
Sample int
usesFloatConst map[float32]bool
usesIntConst map[int]bool
floatConsts []float32
intConsts []int
calls map[string]bool
stackframes map[string][]string
Stacklocs []string
Output16Bit bool
Clip bool
Library bool
Amd64 bool
DisableSections bool
Sine int // TODO: how can we elegantly access global constants in template, without wrapping each one by one
Trisaw int
Pulse int
Gate int
Sample int
usesFloatConst map[float32]bool
usesIntConst map[int]bool
floatConsts []float32
intConsts []int
calls map[string]bool
stackframes map[string][]string
FeatureSet
Compiler
}
@ -44,6 +46,7 @@ func NewMacros(c Compiler, f FeatureSet) *Macros {
Pulse: sointu.Pulse,
Gate: sointu.Gate,
Sample: sointu.Sample,
Amd64: c.Arch == "amd64",
Compiler: c,
FeatureSet: f,
}
@ -449,10 +452,8 @@ type PlayerMacros struct {
EncodedPatch
}
func NewPlayerMacros(c Compiler, f FeatureSet, s *sointu.Song, e *EncodedPatch, maxSamples int) *PlayerMacros {
if maxSamples == 0 {
maxSamples = s.SamplesPerRow() * s.TotalRows()
}
func NewPlayerMacros(c Compiler, f FeatureSet, s *sointu.Song, e *EncodedPatch) *PlayerMacros {
maxSamples := s.SamplesPerRow() * s.TotalRows()
macros := *NewMacros(c, f)
macros.Output16Bit = s.Output16Bit // TODO: should we actually store output16bit in Songs or not?
p := PlayerMacros{Song: s, Macros: macros, MaxSamples: maxSamples, EncodedPatch: *e}