// 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 { DimensionName string Receiver string PowerOffset int // from normal (for example, mass base unit is kg, not g) PrintString string // print string for the unit (kg for mass) ExtraConstant []Constant Name 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 Units = []Unit{ // Base units. { DimensionName: "Dimless", Receiver: "d", TypeComment: "Dimless represents a dimensionless constant", Dimensions: []Dimension{}, }, { DimensionName: "Angle", Receiver: "a", PrintString: "rad", Name: "Rad", TypeComment: "Angle represents an angle in radians", Dimensions: []Dimension{ {Name: AngleName, Power: 1}, }, }, { DimensionName: "Current", Receiver: "i", PrintString: "A", Name: "Ampere", TypeComment: "Current represents a current in Amperes", Dimensions: []Dimension{ {Name: CurrentName, Power: 1}, }, }, { DimensionName: "Length", Receiver: "l", PrintString: "m", Name: "Metre", TypeComment: "Length represents a length in metres", Dimensions: []Dimension{ {Name: LengthName, Power: 1}, }, }, { DimensionName: "LuminousIntensity", Receiver: "j", PrintString: "cd", Name: "Candela", TypeComment: "Candela represents a luminous intensity in candela", Dimensions: []Dimension{ {Name: LuminousIntensityName, Power: 1}, }, }, { DimensionName: "Mass", Receiver: "m", PowerOffset: -3, PrintString: "kg", Name: "Gram", TypeComment: "Mass represents a mass in kilograms", ExtraConstant: []Constant{ {Name: "Kilogram", Value: "Kilo * Gram"}, }, Dimensions: []Dimension{ {Name: MassName, Power: 1}, }, }, { DimensionName: "Mole", Receiver: "n", PrintString: "mol", Name: "Mol", TypeComment: "Mole represents an amount in moles", Dimensions: []Dimension{ {Name: MoleName, Power: 1}, }, }, { DimensionName: "Temperature", Receiver: "t", PrintString: "K", Name: "Kelvin", TypeComment: "Temperature represents a temperature in Kelvin", Dimensions: []Dimension{ {Name: TemperatureName, Power: 1}, }, ErForm: "Temperaturer", }, { DimensionName: "Time", Receiver: "t", PrintString: "s", Name: "Second", TypeComment: "Time represents a duration in seconds", ExtraConstant: []Constant{ {Name: "Minute", Value: "60 * Second"}, {Name: "Hour", Value: "60 * Minute"}, }, Dimensions: []Dimension{ {Name: TimeName, Power: 1}, }, ErForm: "Timer", }, // Derived units. { DimensionName: "AbsorbedRadioactiveDose", Receiver: "a", PrintString: "Gy", Name: "Gray", TypeComment: "AbsorbedRadioactiveDose is a measure of absorbed dose of ionizing radiation in grays", Dimensions: []Dimension{ {Name: LengthName, Power: 2}, {Name: TimeName, Power: -2}, }, }, { DimensionName: "Acceleration", Receiver: "a", PrintString: "m s^-2", TypeComment: "Acceleration represents an acceleration in metres per second squared", Dimensions: []Dimension{ {Name: LengthName, Power: 1}, {Name: TimeName, Power: -2}, }, }, { DimensionName: "Area", Receiver: "a", PrintString: "m^2", TypeComment: "Area represents an area in square metres", Dimensions: []Dimension{ {Name: LengthName, Power: 2}, }, }, { DimensionName: "Radioactivity", Receiver: "r", PrintString: "Bq", Name: "Becquerel", TypeComment: "Radioactivity represents a rate of radioactive decay in becquerels", Dimensions: []Dimension{ {Name: TimeName, Power: -1}, }, }, { DimensionName: "Capacitance", Receiver: "cp", PrintString: "F", Name: "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", }, { DimensionName: "Charge", Receiver: "ch", PrintString: "C", Name: "Coulomb", TypeComment: "Charge represents an electric charge in Coulombs", Dimensions: []Dimension{ {Name: CurrentName, Power: 1}, {Name: TimeName, Power: 1}, }, ErForm: "Charger", }, { DimensionName: "Conductance", Receiver: "co", PrintString: "S", Name: "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", }, { DimensionName: "EquivalentRadioactiveDose", Receiver: "a", PrintString: "Sy", Name: "Sievert", TypeComment: "EquivalentRadioactiveDose is a measure of equivalent dose of ionizing radiation in sieverts", Dimensions: []Dimension{ {Name: LengthName, Power: 2}, {Name: TimeName, Power: -2}, }, }, { DimensionName: "Energy", Receiver: "e", PrintString: "J", Name: "Joule", TypeComment: "Energy represents a quantity of energy in Joules", Dimensions: []Dimension{ {Name: LengthName, Power: 2}, {Name: MassName, Power: 1}, {Name: TimeName, Power: -2}, }, }, { DimensionName: "Frequency", Receiver: "f", PrintString: "Hz", Name: "Hertz", TypeComment: "Frequency represents a frequency in Hertz", Dimensions: []Dimension{ {Name: TimeName, Power: -1}, }, }, { DimensionName: "Force", Receiver: "f", PrintString: "N", Name: "Newton", TypeComment: "Force represents a force in Newtons", Dimensions: []Dimension{ {Name: LengthName, Power: 1}, {Name: MassName, Power: 1}, {Name: TimeName, Power: -2}, }, ErForm: "Forcer", }, { DimensionName: "Inductance", Receiver: "i", PrintString: "H", Name: "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", }, { DimensionName: "Power", Receiver: "pw", PrintString: "W", Name: "Watt", TypeComment: "Power represents a power in Watts", Dimensions: []Dimension{ {Name: LengthName, Power: 2}, {Name: MassName, Power: 1}, {Name: TimeName, Power: -3}, }, }, { DimensionName: "Resistance", Receiver: "r", PrintString: "Ω", Name: "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", }, { DimensionName: "MagneticFlux", Receiver: "m", PrintString: "Wb", Name: "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}, }, }, { DimensionName: "MagneticFluxDensity", Receiver: "m", PrintString: "T", Name: "Tesla", TypeComment: "MagneticFluxDensity represents a magnetic flux density in Tesla", Dimensions: []Dimension{ {Name: CurrentName, Power: -1}, {Name: MassName, Power: 1}, {Name: TimeName, Power: -2}, }, }, { DimensionName: "Pressure", Receiver: "pr", PrintString: "Pa", Name: "Pascal", TypeComment: "Pressure represents a pressure in Pascals", Dimensions: []Dimension{ {Name: LengthName, Power: -1}, {Name: MassName, Power: 1}, {Name: TimeName, Power: -2}, }, ErForm: "Pressurer", }, { DimensionName: "Torque", Receiver: "t", PrintString: "N m", Name: "Newtonmetre", TypeComment: "Torque represents a torque in Newton metres", Dimensions: []Dimension{ {Name: LengthName, Power: 2}, {Name: MassName, Power: 1}, {Name: TimeName, Power: -2}, }, ErForm: "Torquer", }, { DimensionName: "Velocity", Receiver: "v", PrintString: "m s^-1", TypeComment: "Velocity represents a velocity in metres per second", Dimensions: []Dimension{ {Name: LengthName, Power: 1}, {Name: TimeName, Power: -1}, }, }, { DimensionName: "Voltage", Receiver: "v", PrintString: "V", Name: "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", }, { DimensionName: "Volume", Receiver: "v", PowerOffset: -3, PrintString: "m^3", Name: "Litre", TypeComment: "Volume represents a volume in cubic metres", 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" {{if .PrintString}}"unicode/utf8"{{end}} ) // {{.TypeComment}}. type {{.DimensionName}} float64 ` var header = template.Must(template.New("header").Parse(headerTemplate)) const constTemplate = ` const {{if .ExtraConstant}}({{end}} {{.Name}} {{.DimensionName}} = {{if .PowerOffset}} 1e{{.PowerOffset}} {{else}} 1 {{end}} {{$name := .Name}} {{range .ExtraConstant}} {{.Name}} = {{.Value}} {{end}} {{if .ExtraConstant}}){{end}} ` var prefix = template.Must(template.New("prefix").Parse(constTemplate)) const methodTemplate = ` // Unit converts the {{.DimensionName}} to a *Unit. func ({{.Receiver}} {{.DimensionName}}) Unit() *Unit { return New(float64({{.Receiver}}), Dimensions{ {{range .Dimensions}} {{.Name}}: {{.Power}}, {{end}} }) } // {{.DimensionName}} allows {{.DimensionName}} to implement a {{if .ErForm}}{{.ErForm}}{{else}}{{.DimensionName}}er{{end}} interface. func ({{.Receiver}} {{.DimensionName}}) {{.DimensionName}}() {{.DimensionName}} { return {{.Receiver}} } // From converts the unit into the receiver. From returns an // error if there is a mismatch in dimension. func ({{.Receiver}} *{{.DimensionName}}) From(u Uniter) error { if !DimensionsMatch(u, {{if .Name}}{{.Name}}{{else}}{{.DimensionName}}(0){{end}}){ *{{.Receiver}} = {{.DimensionName}}(math.NaN()) return errors.New("unit: dimension mismatch") } *{{.Receiver}} = {{.DimensionName}}(u.Unit().Value()) return nil } ` var methods = template.Must(template.New("methods").Parse(methodTemplate)) const formatTemplate = ` func ({{.Receiver}} {{.DimensionName}}) 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() {{if .PrintString}}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}})) {{else}} switch { case pOk && wOk: fmt.Fprintf(fs, "%*.*"+string(c), w, p, float64({{.Receiver}})) case pOk: fmt.Fprintf(fs, "%.*"+string(c), p, float64({{.Receiver}})) case wOk: fmt.Fprintf(fs, "%*"+string(c), w, float64({{.Receiver}})) default: fmt.Fprintf(fs, "%"+string(c), float64({{.Receiver}})) } default: fmt.Fprintf(fs, "%%!%c(%T=%g)", c, {{.Receiver}}, float64({{.Receiver}})) {{end}} } } ` var form = template.Must(template.New("format").Parse(formatTemplate)) func generate(unit Unit) { lowerName := strings.ToLower(unit.DimensionName) filename := lowerName + ".go" f, err := os.Create(filename) if err != nil { log.Fatal(err) } defer f.Close() var buf bytes.Buffer err = header.Execute(&buf, unit) if err != nil { log.Fatal(err) } if unit.Name != "" { err = prefix.Execute(&buf, unit) 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.DimensionName, 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{{.DimensionName}}(t *testing.T) { t.Parallel() for _, value := range []float64{-1, 0, 1} { var got {{.DimensionName}} err := got.From({{.DimensionName}}(value).Unit()) if err != nil { t.Errorf("unexpected error for %T conversion: %v", got, err) } if got != {{.DimensionName}}(value) { t.Errorf("unexpected result from round trip of %T(%v): got: %v want: %v", got, float64(value), got, value) } if got != got.{{.DimensionName}}() { t.Errorf("unexpected result from self interface method call: got: %#v want: %#v", got, value) } err = got.From(ether(1)) if err == nil { t.Errorf("expected error for ether to %T conversion", got) } } } func Test{{.DimensionName}}Format(t *testing.T) { t.Parallel() for _, test := range []struct{ value {{.DimensionName}} format string want string }{ {1.23456789, "%v", "1.23456789{{with .PrintString}} {{.}}{{end}}"}, {1.23456789, "%.1v", "1{{with .PrintString}} {{.}}{{end}}"}, {1.23456789, "%20.1v", "{{if .PrintString}}{{$s := printf "1 %s" .PrintString}}{{printf "%20s" $s}}{{else}}{{printf "%20s" "1"}}{{end}}"}, {1.23456789, "%20v", "{{if .PrintString}}{{$s := printf "1.23456789 %s" .PrintString}}{{printf "%20s" $s}}{{else}}{{printf "%20s" "1.23456789"}}{{end}}"}, {1.23456789, "%1v", "1.23456789{{with .PrintString}} {{.}}{{end}}"}, {1.23456789, "%#v", "unit.{{.DimensionName}}(1.23456789)"}, {1.23456789, "%s", "%!s(unit.{{.DimensionName}}=1.23456789{{with .PrintString}} {{.}}{{end}})"}, } { 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.DimensionName) filename := lowerName + "_test.go" f, err := os.Create(filename) if err != nil { log.Fatal(err) } defer f.Close() var buf bytes.Buffer 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.DimensionName, err) } f.Write(b) }