Files
gonum/unit/generate_unit.go
2019-03-11 10:32:40 +10:30

744 lines
16 KiB
Go

// 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"
"gonum.org/v1/gonum/unit"
)
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
}
func (u Unit) Units() string {
dims := make(unit.Dimensions)
for _, d := range u.Dimensions {
dims[dimOf[d.Name]] = d.Power
}
return dims.String()
}
const (
AngleName string = "AngleDim"
CurrentName string = "CurrentDim"
LengthName string = "LengthDim"
LuminousIntensityName string = "LuminousIntensityDim"
MassName string = "MassDim"
MoleName string = "MoleDim"
TemperatureName string = "TemperatureDim"
TimeName string = "TimeDim"
)
var dimOf = map[string]unit.Dimension{
"AngleDim": unit.AngleDim,
"CurrentDim": unit.CurrentDim,
"LengthDim": unit.LengthDim,
"LuminousIntensityDim": unit.LuminousIntensityDim,
"MassDim": unit.MassDim,
"MoleDim": unit.MoleDim,
"TemperatureDim": unit.TemperatureDim,
"TimeDim": unit.TimeDim,
}
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{
// Base units.
{
Name: "Angle",
Receiver: "a",
PrintString: "rad",
Suffix: "rad",
Singular: "Rad",
TypeComment: "Angle represents an angle in radians",
Dimensions: []Dimension{
{Name: AngleName, Power: 1},
},
},
{
Name: "Current",
Receiver: "i",
PrintString: "A",
Suffix: "ampere",
Singular: "Ampere",
TypeComment: "Current represents a current in Amperes",
Dimensions: []Dimension{
{Name: CurrentName, 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: "LuminousIntensity",
Receiver: "j",
PrintString: "cd",
Suffix: "candela",
Singular: "Candela",
TypeComment: "Candela represents a luminous intensity in candela",
Dimensions: []Dimension{
{Name: LuminousIntensityName, Power: 1},
},
},
{
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: "Mole",
Receiver: "n",
PrintString: "mol",
Suffix: "mol",
Singular: "mol",
TypeComment: "Mole represents an amount in moles",
Dimensions: []Dimension{
{Name: MoleName, Power: 1},
},
},
{
Name: "Temperature",
Receiver: "t",
PrintString: "K",
Suffix: "kelvin",
Singular: "Kelvin",
TypeComment: "Temperature represents a temperature in Kelvin",
Dimensions: []Dimension{
{Name: TemperatureName, Power: 1},
},
ErForm: "Temperaturer",
},
{
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",
},
// Derived units.
{
Name: "AbsorbedRadioactiveDose",
Receiver: "a",
PrintString: "Gy",
Suffix: "gray",
Singular: "Gray",
TypeComment: "AbsorbedRadioactiveDose is a measure of absorbed dose of ionizing radiation in grays",
Dimensions: []Dimension{
{Name: LengthName, Power: 2},
{Name: TimeName, Power: -2},
},
},
{
Name: "Acceleration",
Receiver: "a",
PrintString: "m s^-2",
TypeComment: "Acceleration represents an acceleration in meters per second squared",
Dimensions: []Dimension{
{Name: LengthName, Power: 1},
{Name: TimeName, Power: -2},
},
},
{
Name: "Area",
Receiver: "a",
PrintString: "m^2",
TypeComment: "Area represents and area in square meters",
Dimensions: []Dimension{
{Name: LengthName, Power: 2},
},
},
{
Name: "Radioactivity",
Receiver: "r",
PrintString: "Bq",
Suffix: "becquerel",
Singular: "Becquerel",
TypeComment: "Radioactivity represents a rate of radioactive decay in becquerels",
Dimensions: []Dimension{
{Name: TimeName, Power: -1},
},
},
{
Name: "Capacitance",
Receiver: "cp",
PrintString: "F",
Suffix: "farad",
Singular: "Farad",
TypeComment: "Capacitance represents an electrical capacitance in Farads",
Dimensions: []Dimension{
{Name: CurrentName, Power: 2},
{Name: LengthName, Power: -2},
{Name: MassName, Power: -1},
{Name: TimeName, Power: 4},
},
ErForm: "Capacitancer",
},
{
Name: "Charge",
Receiver: "ch",
PrintString: "C",
Suffix: "coulomb",
Singular: "coulomb",
TypeComment: "Charge represents an electric charge in Coulombs",
Dimensions: []Dimension{
{Name: CurrentName, Power: 1},
{Name: TimeName, Power: 1},
},
ErForm: "Charger",
},
{
Name: "Conductance",
Receiver: "co",
PrintString: "S",
Suffix: "siemens",
Singular: "Siemens",
TypeComment: "Conductance represents an electrical conductance in Siemens",
Dimensions: []Dimension{
{Name: CurrentName, Power: 2},
{Name: LengthName, Power: -2},
{Name: MassName, Power: -1},
{Name: TimeName, Power: 3},
},
ErForm: "Conductancer",
},
{
Name: "EquivalentRadioactiveDose",
Receiver: "a",
PrintString: "Sy",
Suffix: "sievert",
Singular: "Sievert",
TypeComment: "EquivalentRadioactiveDose is a measure of equivalent dose of ionizing radiation in sieverts",
Dimensions: []Dimension{
{Name: LengthName, Power: 2},
{Name: TimeName, Power: -2},
},
},
{
Name: "Energy",
Receiver: "e",
PrintString: "J",
Suffix: "joule",
Singular: "Joule",
TypeComment: "Energy represents a quantity of energy in Joules",
Dimensions: []Dimension{
{Name: LengthName, Power: 2},
{Name: MassName, Power: 1},
{Name: TimeName, Power: -2},
},
},
{
Name: "Frequency",
Receiver: "f",
PrintString: "Hz",
Suffix: "hertz",
Singular: "Hertz",
TypeComment: "Frequency represents a frequency in Hertz",
Dimensions: []Dimension{
{Name: TimeName, Power: -1},
},
},
{
Name: "Force",
Receiver: "f",
PrintString: "N",
Suffix: "newton",
Singular: "Newton",
TypeComment: "Force represents a force in Newtons",
Dimensions: []Dimension{
{Name: LengthName, Power: 1},
{Name: MassName, Power: 1},
{Name: TimeName, Power: -2},
},
ErForm: "Forcer",
},
{
Name: "Inductance",
Receiver: "i",
PrintString: "H",
Suffix: "henry",
Singular: "Henry",
TypeComment: "Inductance represents an electrical inductance in Henry",
Dimensions: []Dimension{
{Name: CurrentName, Power: -2},
{Name: LengthName, Power: 2},
{Name: MassName, Power: 1},
{Name: TimeName, Power: -2},
},
ErForm: "Inductancer",
},
{
Name: "Power",
Receiver: "pw",
PrintString: "W",
Suffix: "watt",
Singular: "Watt",
TypeComment: "Power represents a power in Watts",
Dimensions: []Dimension{
{Name: LengthName, Power: 2},
{Name: MassName, Power: 1},
{Name: TimeName, Power: -3},
},
},
{
Name: "Resistance",
Receiver: "r",
PrintString: "Ω",
Suffix: "ohm",
Singular: "Ohm",
TypeComment: "Resistance represents an electrical resistance, impedance or reactance in Ohms",
Dimensions: []Dimension{
{Name: CurrentName, Power: -2},
{Name: LengthName, Power: 2},
{Name: MassName, Power: 1},
{Name: TimeName, Power: -3},
},
ErForm: "Resistancer",
},
{
Name: "MagneticFlux",
Receiver: "m",
PrintString: "Wb",
Suffix: "weber",
Singular: "Weber",
TypeComment: "MagneticFlux represents a magnetic flux in Weber",
Dimensions: []Dimension{
{Name: CurrentName, Power: -1},
{Name: LengthName, Power: 2},
{Name: MassName, Power: 1},
{Name: TimeName, Power: -2},
},
},
{
Name: "MagneticFluxDensity",
Receiver: "m",
PrintString: "T",
Suffix: "tesla",
Singular: "Tesla",
TypeComment: "MagneticFluxDensity represents a magnetic flux density in Tesla",
Dimensions: []Dimension{
{Name: CurrentName, Power: -1},
{Name: MassName, Power: 1},
{Name: TimeName, Power: -2},
},
},
{
Name: "Pressure",
Receiver: "pr",
PrintString: "Pa",
Suffix: "pascal",
Singular: "Pascal",
TypeComment: "Pressure represents a pressure in Pascals",
Dimensions: []Dimension{
{Name: LengthName, Power: -1},
{Name: MassName, Power: 1},
{Name: TimeName, Power: -2},
},
ErForm: "Pressurer",
},
{
Name: "Velocity",
Receiver: "v",
PrintString: "m s^-1",
TypeComment: "Velocity represents a velocity in meters per second",
Dimensions: []Dimension{
{Name: LengthName, Power: 1},
{Name: TimeName, Power: -1},
},
},
{
Name: "Voltage",
Receiver: "v",
PrintString: "V",
Suffix: "volt",
Singular: "Volt",
TypeComment: "Voltage represents a voltage in Volts",
Dimensions: []Dimension{
{Name: CurrentName, Power: -1},
{Name: LengthName, Power: 2},
{Name: MassName, Power: 1},
{Name: TimeName, Power: -3},
},
ErForm: "Voltager",
},
{
Name: "Volume",
Receiver: "v",
Offset: -3,
PrintString: "m^3",
Suffix: "liter",
Singular: "Liter",
TypeComment: "Volume represents a volume in cubic meters",
Dimensions: []Dimension{
{Name: LengthName, Power: 3},
},
},
}
// Generate generates a file for each of the units
func main() {
for _, unit := range Units {
generate(unit)
generateTest(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"
"unicode/utf8"
)
// {{.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, {{if .Singular}}{{.Singular}}{{else}}{{.Name}}(0){{end}}){
*{{.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-utf8.RuneCount([]byte(unit))), p, float64({{.Receiver}}))
case pOk:
fmt.Fprintf(fs, "%.*"+string(c), p, float64({{.Receiver}}))
case wOk:
fmt.Fprintf(fs, "%*"+string(c), pos(w-utf8.RuneCount([]byte(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)
}
if unit.Singular != "" {
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 %q: %s", unit.Name, err)
}
f.Write(b)
}
const testTemplate = `// Code generated by "go generate gonum.org/v1/gonum/unit; DO NOT EDIT.
// Copyright ©2019 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 (
"fmt"
"testing"
)
func Test{{.Name}}Format(t *testing.T) {
for _, test := range []struct{
value {{.Name}}
format string
want string
}{
{1.23456789, "%v", "1.23456789 {{.PrintString}}"},
{1.23456789, "%.1v", "1 {{.PrintString}}"},
{1.23456789, "%20.1v", "{{$s := printf "1 %s" .PrintString}}{{printf "%20s" $s}}"},
{1.23456789, "%20v", "{{$s := printf "1.23456789 %s" .PrintString}}{{printf "%20s" $s}}"},
{1.23456789, "%1v", "1.23456789 {{.PrintString}}"},
{1.23456789, "%#v", "unit.{{.Name}}(1.23456789)"},
{1.23456789, "%s", "%!s(unit.{{.Name}}=1.23456789 {{.PrintString}})"},
} {
got := fmt.Sprintf(test.format, test.value)
if got != test.want {
t.Errorf("Format %q %v: got: %q want: %q", test.format, float64(test.value), got, test.want)
}
}
}
`
var tests = template.Must(template.New("test").Parse(testTemplate))
func generateTest(unit Unit) {
lowerName := strings.ToLower(unit.Name)
filename := lowerName + "_test.go"
f, err := os.Create(filename)
if err != nil {
log.Fatal(err)
}
defer f.Close()
buf := bytes.NewBuffer(make([]byte, 0))
err = tests.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 test for %q: %s", unit.Name, err)
}
f.Write(b)
}