| // 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) |
| } |