// Copyright ©2014 The Gonum Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // +build ignore package main import ( "bytes" "go/format" "log" "os" "strings" "text/template" ) type Unit struct { Name string Receiver string Offset int // From normal (for example, mass base unit is kg, not kg) PrintString string // print string for the unit (kg for mass) ExtraConstant []Constant Suffix string Singular string TypeComment string // Text to comment the type Dimensions []Dimension ErForm string //For Xxxer interface } type Dimension struct { Name string Power int } const ( TimeName string = "TimeDim" LengthName string = "LengthDim" MassName string = "MassDim" ) type Constant struct { Name string Value string } type Prefix struct { Name string Power int } var Prefixes = []Prefix{ { Name: "Yotta", Power: 24, }, { Name: "Zetta", Power: 21, }, { Name: "Exa", Power: 18, }, { Name: "Peta", Power: 15, }, { Name: "Tera", Power: 12, }, { Name: "Giga", Power: 9, }, { Name: "Mega", Power: 6, }, { Name: "Kilo", Power: 3, }, { Name: "Hecto", Power: 2, }, { Name: "Deca", Power: 1, }, { Name: "", Power: 0, }, { Name: "Deci", Power: -1, }, { Name: "Centi", Power: -2, }, { Name: "Milli", Power: -3, }, { Name: "Micro", Power: -6, }, { Name: "Nano", Power: -9, }, { Name: "Pico", Power: -12, }, { Name: "Femto", Power: -15, }, { Name: "Atto", Power: -18, }, { Name: "Zepto", Power: -21, }, { Name: "Yocto", Power: -24, }, } var Units = []Unit{ { Name: "Mass", Receiver: "m", Offset: -3, PrintString: "kg", Suffix: "gram", Singular: "Gram", TypeComment: "Mass represents a mass in kilograms", Dimensions: []Dimension{ { Name: MassName, Power: 1, }, }, }, { Name: "Length", Receiver: "l", PrintString: "m", Suffix: "meter", Singular: "Meter", TypeComment: "Length represents a length in meters", Dimensions: []Dimension{ { Name: LengthName, Power: 1, }, }, }, { Name: "Time", Receiver: "t", PrintString: "s", Suffix: "second", Singular: "Second", TypeComment: "Time represents a time in seconds", ExtraConstant: []Constant{ { Name: "Hour", Value: "3600", }, { Name: "Minute", Value: "60", }, }, Dimensions: []Dimension{ { Name: TimeName, Power: 1, }, }, ErForm: "Timer", }, } // Generate generates a file for each of the units func main() { for _, unit := range Units { generate(unit) } } const headerTemplate = `// Code generated by "go generate gonum.org/v1/gonum/unit”; DO NOT EDIT. // Copyright ©2014 The Gonum Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package unit import ( "errors" "fmt" "math" ) // {{.TypeComment}} type {{.Name}} float64 ` var header = template.Must(template.New("header").Parse(headerTemplate)) const constTemplate = ` const( {{$unit := .Unit}} {{range $unit.ExtraConstant}} {{.Name}} {{$unit.Name}} = {{.Value}} {{end}} {{$prefixes := .Prefixes}} {{range $prefixes}} {{if .Name}} {{.Name}}{{$unit.Suffix}} {{else}} {{$unit.Singular}} {{end}} {{$unit.Name}} = {{if .Power}} 1e{{.Power}} {{else}} 1.0 {{end}} {{end}} ) ` var prefix = template.Must(template.New("prefix").Parse(constTemplate)) const methodTemplate = ` // Unit converts the {{.Name}} to a *Unit func ({{.Receiver}} {{.Name}}) Unit() *Unit { return New(float64({{.Receiver}}), Dimensions{ {{range .Dimensions}} {{.Name}}: {{.Power}}, {{end}} }) } // {{.Name}} allows {{.Name}} to implement a {{if .ErForm}}{{.ErForm}}{{else}}{{.Name}}er{{end}} interface func ({{.Receiver}} {{.Name}}) {{.Name}}() {{.Name}} { return {{.Receiver}} } // From converts the unit into the receiver. From returns an // error if there is a mismatch in dimension func ({{.Receiver}} *{{.Name}}) From(u Uniter) error { if !DimensionsMatch(u, {{.Singular}}){ *{{.Receiver}} = {{.Name}}(math.NaN()) return errors.New("Dimension mismatch") } *{{.Receiver}} = {{.Name}}(u.Unit().Value()) return nil } ` var methods = template.Must(template.New("methods").Parse(methodTemplate)) const formatTemplate = ` func ({{.Receiver}} {{.Name}}) Format(fs fmt.State, c rune) { switch c { case 'v': if fs.Flag('#') { fmt.Fprintf(fs, "%T(%v)", {{.Receiver}}, float64({{.Receiver}})) return } fallthrough case 'e', 'E', 'f', 'F', 'g', 'G': p, pOk := fs.Precision() w, wOk := fs.Width() const unit = " {{.PrintString}}" switch { case pOk && wOk: fmt.Fprintf(fs, "%*.*"+string(c), pos(w-len(unit)), p, float64({{.Receiver}})) case pOk: fmt.Fprintf(fs, "%.*"+string(c), p, float64({{.Receiver}})) case wOk: fmt.Fprintf(fs, "%*"+string(c), pos(w-len(unit)), float64({{.Receiver}})) default: fmt.Fprintf(fs, "%"+string(c), float64({{.Receiver}})) } fmt.Fprint(fs, unit) default: fmt.Fprintf(fs, "%%!%c(%T=%g {{.PrintString}})", c, {{.Receiver}}, float64({{.Receiver}})) } } ` var form = template.Must(template.New("format").Parse(formatTemplate)) func generate(unit Unit) { lowerName := strings.ToLower(unit.Name) filename := lowerName + ".go" f, err := os.Create(filename) if err != nil { log.Fatal(err) } defer f.Close() // Need to define new prefixes because text/template can't do math. // Need to do math because kilogram = 1 not 10^3 prefixes := make([]Prefix, len(Prefixes)) for i, p := range Prefixes { prefixes[i].Name = p.Name prefixes[i].Power = p.Power + unit.Offset } data := struct { Prefixes []Prefix Unit Unit }{ prefixes, unit, } buf := bytes.NewBuffer(make([]byte, 0)) err = header.Execute(buf, unit) if err != nil { log.Fatal(err) } err = prefix.Execute(buf, data) if err != nil { log.Fatal(err) } err = methods.Execute(buf, unit) if err != nil { log.Fatal(err) } err = form.Execute(buf, unit) if err != nil { log.Fatal(err) } b, err := format.Source(buf.Bytes()) if err != nil { f.Write(buf.Bytes()) // This is here to debug bad format log.Fatalf("error formatting: %s", err) } f.Write(b) }