blob: 7a09d17d53c0900a82afef8293cf456134aca810 [file] [log] [blame]
// 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)
}