github.com/gopherd/gonum@v0.0.4/unit/generate_unit.go (about) 1 // Copyright ©2014 The Gonum Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 //go:build ignore 6 // +build ignore 7 8 package main 9 10 import ( 11 "bytes" 12 "go/format" 13 "log" 14 "os" 15 "strings" 16 "text/template" 17 18 "github.com/gopherd/gonum/unit" 19 ) 20 21 type Unit struct { 22 DimensionName string 23 Receiver string 24 PowerOffset int // from normal (for example, mass base unit is kg, not g) 25 PrintString string // print string for the unit (kg for mass) 26 ExtraConstant []Constant 27 Name string 28 TypeComment string // text to comment the type 29 Dimensions []Dimension 30 ErForm string // for Xxxer interface 31 } 32 33 type Dimension struct { 34 Name string 35 Power int 36 } 37 38 func (u Unit) Units() string { 39 dims := make(unit.Dimensions) 40 for _, d := range u.Dimensions { 41 dims[dimOf[d.Name]] = d.Power 42 } 43 return dims.String() 44 } 45 46 const ( 47 AngleName string = "AngleDim" 48 CurrentName string = "CurrentDim" 49 LengthName string = "LengthDim" 50 LuminousIntensityName string = "LuminousIntensityDim" 51 MassName string = "MassDim" 52 MoleName string = "MoleDim" 53 TemperatureName string = "TemperatureDim" 54 TimeName string = "TimeDim" 55 ) 56 57 var dimOf = map[string]unit.Dimension{ 58 "AngleDim": unit.AngleDim, 59 "CurrentDim": unit.CurrentDim, 60 "LengthDim": unit.LengthDim, 61 "LuminousIntensityDim": unit.LuminousIntensityDim, 62 "MassDim": unit.MassDim, 63 "MoleDim": unit.MoleDim, 64 "TemperatureDim": unit.TemperatureDim, 65 "TimeDim": unit.TimeDim, 66 } 67 68 type Constant struct { 69 Name string 70 Value string 71 } 72 73 type Prefix struct { 74 Name string 75 Power int 76 } 77 78 var Units = []Unit{ 79 // Base units. 80 { 81 DimensionName: "Dimless", 82 Receiver: "d", 83 TypeComment: "Dimless represents a dimensionless constant", 84 Dimensions: []Dimension{}, 85 }, 86 { 87 DimensionName: "Angle", 88 Receiver: "a", 89 PrintString: "rad", 90 Name: "Rad", 91 TypeComment: "Angle represents an angle in radians", 92 Dimensions: []Dimension{ 93 {Name: AngleName, Power: 1}, 94 }, 95 }, 96 { 97 DimensionName: "Current", 98 Receiver: "i", 99 PrintString: "A", 100 Name: "Ampere", 101 TypeComment: "Current represents a current in Amperes", 102 Dimensions: []Dimension{ 103 {Name: CurrentName, Power: 1}, 104 }, 105 }, 106 { 107 DimensionName: "Length", 108 Receiver: "l", 109 PrintString: "m", 110 Name: "Metre", 111 TypeComment: "Length represents a length in metres", 112 Dimensions: []Dimension{ 113 {Name: LengthName, Power: 1}, 114 }, 115 }, 116 { 117 DimensionName: "LuminousIntensity", 118 Receiver: "j", 119 PrintString: "cd", 120 Name: "Candela", 121 TypeComment: "Candela represents a luminous intensity in candela", 122 Dimensions: []Dimension{ 123 {Name: LuminousIntensityName, Power: 1}, 124 }, 125 }, 126 { 127 DimensionName: "Mass", 128 Receiver: "m", 129 PowerOffset: -3, 130 PrintString: "kg", 131 Name: "Gram", 132 TypeComment: "Mass represents a mass in kilograms", 133 ExtraConstant: []Constant{ 134 {Name: "Kilogram", Value: "Kilo * Gram"}, 135 }, 136 Dimensions: []Dimension{ 137 {Name: MassName, Power: 1}, 138 }, 139 }, 140 { 141 DimensionName: "Mole", 142 Receiver: "n", 143 PrintString: "mol", 144 Name: "Mol", 145 TypeComment: "Mole represents an amount in moles", 146 Dimensions: []Dimension{ 147 {Name: MoleName, Power: 1}, 148 }, 149 }, 150 { 151 DimensionName: "Temperature", 152 Receiver: "t", 153 PrintString: "K", 154 Name: "Kelvin", 155 TypeComment: "Temperature represents a temperature in Kelvin", 156 Dimensions: []Dimension{ 157 {Name: TemperatureName, Power: 1}, 158 }, 159 ErForm: "Temperaturer", 160 }, 161 { 162 DimensionName: "Time", 163 Receiver: "t", 164 PrintString: "s", 165 Name: "Second", 166 TypeComment: "Time represents a duration in seconds", 167 ExtraConstant: []Constant{ 168 {Name: "Minute", Value: "60 * Second"}, 169 {Name: "Hour", Value: "60 * Minute"}, 170 }, 171 Dimensions: []Dimension{ 172 {Name: TimeName, Power: 1}, 173 }, 174 ErForm: "Timer", 175 }, 176 177 // Derived units. 178 { 179 DimensionName: "AbsorbedRadioactiveDose", 180 Receiver: "a", 181 PrintString: "Gy", 182 Name: "Gray", 183 TypeComment: "AbsorbedRadioactiveDose is a measure of absorbed dose of ionizing radiation in grays", 184 Dimensions: []Dimension{ 185 {Name: LengthName, Power: 2}, 186 {Name: TimeName, Power: -2}, 187 }, 188 }, 189 { 190 DimensionName: "Acceleration", 191 Receiver: "a", 192 PrintString: "m s^-2", 193 TypeComment: "Acceleration represents an acceleration in metres per second squared", 194 Dimensions: []Dimension{ 195 {Name: LengthName, Power: 1}, 196 {Name: TimeName, Power: -2}, 197 }, 198 }, 199 { 200 DimensionName: "Area", 201 Receiver: "a", 202 PrintString: "m^2", 203 TypeComment: "Area represents an area in square metres", 204 Dimensions: []Dimension{ 205 {Name: LengthName, Power: 2}, 206 }, 207 }, 208 { 209 DimensionName: "Radioactivity", 210 Receiver: "r", 211 PrintString: "Bq", 212 Name: "Becquerel", 213 TypeComment: "Radioactivity represents a rate of radioactive decay in becquerels", 214 Dimensions: []Dimension{ 215 {Name: TimeName, Power: -1}, 216 }, 217 }, 218 { 219 DimensionName: "Capacitance", 220 Receiver: "cp", 221 PrintString: "F", 222 Name: "Farad", 223 TypeComment: "Capacitance represents an electrical capacitance in Farads", 224 Dimensions: []Dimension{ 225 {Name: CurrentName, Power: 2}, 226 {Name: LengthName, Power: -2}, 227 {Name: MassName, Power: -1}, 228 {Name: TimeName, Power: 4}, 229 }, 230 ErForm: "Capacitancer", 231 }, 232 { 233 DimensionName: "Charge", 234 Receiver: "ch", 235 PrintString: "C", 236 Name: "Coulomb", 237 TypeComment: "Charge represents an electric charge in Coulombs", 238 Dimensions: []Dimension{ 239 {Name: CurrentName, Power: 1}, 240 {Name: TimeName, Power: 1}, 241 }, 242 ErForm: "Charger", 243 }, 244 { 245 DimensionName: "Conductance", 246 Receiver: "co", 247 PrintString: "S", 248 Name: "Siemens", 249 TypeComment: "Conductance represents an electrical conductance in Siemens", 250 Dimensions: []Dimension{ 251 {Name: CurrentName, Power: 2}, 252 {Name: LengthName, Power: -2}, 253 {Name: MassName, Power: -1}, 254 {Name: TimeName, Power: 3}, 255 }, 256 ErForm: "Conductancer", 257 }, 258 { 259 DimensionName: "EquivalentRadioactiveDose", 260 Receiver: "a", 261 PrintString: "Sy", 262 Name: "Sievert", 263 TypeComment: "EquivalentRadioactiveDose is a measure of equivalent dose of ionizing radiation in sieverts", 264 Dimensions: []Dimension{ 265 {Name: LengthName, Power: 2}, 266 {Name: TimeName, Power: -2}, 267 }, 268 }, 269 { 270 DimensionName: "Energy", 271 Receiver: "e", 272 PrintString: "J", 273 Name: "Joule", 274 TypeComment: "Energy represents a quantity of energy in Joules", 275 Dimensions: []Dimension{ 276 {Name: LengthName, Power: 2}, 277 {Name: MassName, Power: 1}, 278 {Name: TimeName, Power: -2}, 279 }, 280 }, 281 { 282 DimensionName: "Frequency", 283 Receiver: "f", 284 PrintString: "Hz", 285 Name: "Hertz", 286 TypeComment: "Frequency represents a frequency in Hertz", 287 Dimensions: []Dimension{ 288 {Name: TimeName, Power: -1}, 289 }, 290 }, 291 { 292 DimensionName: "Force", 293 Receiver: "f", 294 PrintString: "N", 295 Name: "Newton", 296 TypeComment: "Force represents a force in Newtons", 297 Dimensions: []Dimension{ 298 {Name: LengthName, Power: 1}, 299 {Name: MassName, Power: 1}, 300 {Name: TimeName, Power: -2}, 301 }, 302 ErForm: "Forcer", 303 }, 304 { 305 DimensionName: "Inductance", 306 Receiver: "i", 307 PrintString: "H", 308 Name: "Henry", 309 TypeComment: "Inductance represents an electrical inductance in Henry", 310 Dimensions: []Dimension{ 311 {Name: CurrentName, Power: -2}, 312 {Name: LengthName, Power: 2}, 313 {Name: MassName, Power: 1}, 314 {Name: TimeName, Power: -2}, 315 }, 316 ErForm: "Inductancer", 317 }, 318 { 319 DimensionName: "Power", 320 Receiver: "pw", 321 PrintString: "W", 322 Name: "Watt", 323 TypeComment: "Power represents a power in Watts", 324 Dimensions: []Dimension{ 325 {Name: LengthName, Power: 2}, 326 {Name: MassName, Power: 1}, 327 {Name: TimeName, Power: -3}, 328 }, 329 }, 330 { 331 DimensionName: "Resistance", 332 Receiver: "r", 333 PrintString: "Ω", 334 Name: "Ohm", 335 TypeComment: "Resistance represents an electrical resistance, impedance or reactance in Ohms", 336 Dimensions: []Dimension{ 337 {Name: CurrentName, Power: -2}, 338 {Name: LengthName, Power: 2}, 339 {Name: MassName, Power: 1}, 340 {Name: TimeName, Power: -3}, 341 }, 342 ErForm: "Resistancer", 343 }, 344 { 345 DimensionName: "MagneticFlux", 346 Receiver: "m", 347 PrintString: "Wb", 348 Name: "Weber", 349 TypeComment: "MagneticFlux represents a magnetic flux in Weber", 350 Dimensions: []Dimension{ 351 {Name: CurrentName, Power: -1}, 352 {Name: LengthName, Power: 2}, 353 {Name: MassName, Power: 1}, 354 {Name: TimeName, Power: -2}, 355 }, 356 }, 357 { 358 DimensionName: "MagneticFluxDensity", 359 Receiver: "m", 360 PrintString: "T", 361 Name: "Tesla", 362 TypeComment: "MagneticFluxDensity represents a magnetic flux density in Tesla", 363 Dimensions: []Dimension{ 364 {Name: CurrentName, Power: -1}, 365 {Name: MassName, Power: 1}, 366 {Name: TimeName, Power: -2}, 367 }, 368 }, 369 { 370 DimensionName: "Pressure", 371 Receiver: "pr", 372 PrintString: "Pa", 373 Name: "Pascal", 374 TypeComment: "Pressure represents a pressure in Pascals", 375 Dimensions: []Dimension{ 376 {Name: LengthName, Power: -1}, 377 {Name: MassName, Power: 1}, 378 {Name: TimeName, Power: -2}, 379 }, 380 ErForm: "Pressurer", 381 }, 382 { 383 DimensionName: "Torque", 384 Receiver: "t", 385 PrintString: "N m", 386 Name: "Newtonmetre", 387 TypeComment: "Torque represents a torque in Newton metres", 388 Dimensions: []Dimension{ 389 {Name: LengthName, Power: 2}, 390 {Name: MassName, Power: 1}, 391 {Name: TimeName, Power: -2}, 392 }, 393 ErForm: "Torquer", 394 }, 395 { 396 DimensionName: "Velocity", 397 Receiver: "v", 398 PrintString: "m s^-1", 399 TypeComment: "Velocity represents a velocity in metres per second", 400 Dimensions: []Dimension{ 401 {Name: LengthName, Power: 1}, 402 {Name: TimeName, Power: -1}, 403 }, 404 }, 405 { 406 DimensionName: "Voltage", 407 Receiver: "v", 408 PrintString: "V", 409 Name: "Volt", 410 TypeComment: "Voltage represents a voltage in Volts", 411 Dimensions: []Dimension{ 412 {Name: CurrentName, Power: -1}, 413 {Name: LengthName, Power: 2}, 414 {Name: MassName, Power: 1}, 415 {Name: TimeName, Power: -3}, 416 }, 417 ErForm: "Voltager", 418 }, 419 { 420 DimensionName: "Volume", 421 Receiver: "v", 422 PowerOffset: -3, 423 PrintString: "m^3", 424 Name: "Litre", 425 TypeComment: "Volume represents a volume in cubic metres", 426 Dimensions: []Dimension{ 427 {Name: LengthName, Power: 3}, 428 }, 429 }, 430 } 431 432 // Generate generates a file for each of the units 433 func main() { 434 for _, unit := range Units { 435 generate(unit) 436 generateTest(unit) 437 } 438 } 439 440 const headerTemplate = `// Code generated by "go generate github.com/gopherd/gonum/unit”; DO NOT EDIT. 441 442 // Copyright ©2014 The Gonum Authors. All rights reserved. 443 // Use of this source code is governed by a BSD-style 444 // license that can be found in the LICENSE file. 445 446 package unit 447 448 import ( 449 "errors" 450 "fmt" 451 "math" 452 {{if .PrintString}}"unicode/utf8"{{end}} 453 ) 454 455 // {{.TypeComment}}. 456 type {{.DimensionName}} float64 457 ` 458 459 var header = template.Must(template.New("header").Parse(headerTemplate)) 460 461 const constTemplate = ` 462 const {{if .ExtraConstant}}({{end}} 463 {{.Name}} {{.DimensionName}} = {{if .PowerOffset}} 1e{{.PowerOffset}} {{else}} 1 {{end}} 464 {{$name := .Name}} 465 {{range .ExtraConstant}} {{.Name}} = {{.Value}} 466 {{end}} 467 {{if .ExtraConstant}}){{end}} 468 ` 469 470 var prefix = template.Must(template.New("prefix").Parse(constTemplate)) 471 472 const methodTemplate = ` 473 // Unit converts the {{.DimensionName}} to a *Unit. 474 func ({{.Receiver}} {{.DimensionName}}) Unit() *Unit { 475 return New(float64({{.Receiver}}), Dimensions{ 476 {{range .Dimensions}} {{.Name}}: {{.Power}}, 477 {{end}} 478 }) 479 } 480 481 // {{.DimensionName}} allows {{.DimensionName}} to implement a {{if .ErForm}}{{.ErForm}}{{else}}{{.DimensionName}}er{{end}} interface. 482 func ({{.Receiver}} {{.DimensionName}}) {{.DimensionName}}() {{.DimensionName}} { 483 return {{.Receiver}} 484 } 485 486 // From converts the unit into the receiver. From returns an 487 // error if there is a mismatch in dimension. 488 func ({{.Receiver}} *{{.DimensionName}}) From(u Uniter) error { 489 if !DimensionsMatch(u, {{if .Name}}{{.Name}}{{else}}{{.DimensionName}}(0){{end}}){ 490 *{{.Receiver}} = {{.DimensionName}}(math.NaN()) 491 return errors.New("unit: dimension mismatch") 492 } 493 *{{.Receiver}} = {{.DimensionName}}(u.Unit().Value()) 494 return nil 495 } 496 ` 497 498 var methods = template.Must(template.New("methods").Parse(methodTemplate)) 499 500 const formatTemplate = ` 501 func ({{.Receiver}} {{.DimensionName}}) Format(fs fmt.State, c rune) { 502 switch c { 503 case 'v': 504 if fs.Flag('#') { 505 fmt.Fprintf(fs, "%T(%v)", {{.Receiver}}, float64({{.Receiver}})) 506 return 507 } 508 fallthrough 509 case 'e', 'E', 'f', 'F', 'g', 'G': 510 p, pOk := fs.Precision() 511 w, wOk := fs.Width() 512 {{if .PrintString}}const unit = " {{.PrintString}}" 513 switch { 514 case pOk && wOk: 515 fmt.Fprintf(fs, "%*.*"+string(c), pos(w-utf8.RuneCount([]byte(unit))), p, float64({{.Receiver}})) 516 case pOk: 517 fmt.Fprintf(fs, "%.*"+string(c), p, float64({{.Receiver}})) 518 case wOk: 519 fmt.Fprintf(fs, "%*"+string(c), pos(w-utf8.RuneCount([]byte(unit))), float64({{.Receiver}})) 520 default: 521 fmt.Fprintf(fs, "%"+string(c), float64({{.Receiver}})) 522 } 523 fmt.Fprint(fs, unit) 524 default: 525 fmt.Fprintf(fs, "%%!%c(%T=%g {{.PrintString}})", c, {{.Receiver}}, float64({{.Receiver}})) {{else}} switch { 526 case pOk && wOk: 527 fmt.Fprintf(fs, "%*.*"+string(c), w, p, float64({{.Receiver}})) 528 case pOk: 529 fmt.Fprintf(fs, "%.*"+string(c), p, float64({{.Receiver}})) 530 case wOk: 531 fmt.Fprintf(fs, "%*"+string(c), w, float64({{.Receiver}})) 532 default: 533 fmt.Fprintf(fs, "%"+string(c), float64({{.Receiver}})) 534 } 535 default: 536 fmt.Fprintf(fs, "%%!%c(%T=%g)", c, {{.Receiver}}, float64({{.Receiver}})) {{end}} 537 } 538 } 539 ` 540 541 var form = template.Must(template.New("format").Parse(formatTemplate)) 542 543 func generate(unit Unit) { 544 lowerName := strings.ToLower(unit.DimensionName) 545 filename := lowerName + ".go" 546 f, err := os.Create(filename) 547 if err != nil { 548 log.Fatal(err) 549 } 550 defer f.Close() 551 552 var buf bytes.Buffer 553 554 err = header.Execute(&buf, unit) 555 if err != nil { 556 log.Fatal(err) 557 } 558 559 if unit.Name != "" { 560 err = prefix.Execute(&buf, unit) 561 if err != nil { 562 log.Fatal(err) 563 } 564 } 565 566 err = methods.Execute(&buf, unit) 567 if err != nil { 568 log.Fatal(err) 569 } 570 571 err = form.Execute(&buf, unit) 572 if err != nil { 573 log.Fatal(err) 574 } 575 576 b, err := format.Source(buf.Bytes()) 577 if err != nil { 578 f.Write(buf.Bytes()) // This is here to debug bad format 579 log.Fatalf("error formatting %q: %s", unit.DimensionName, err) 580 } 581 582 f.Write(b) 583 } 584 585 const testTemplate = `// Code generated by "go generate github.com/gopherd/gonum/unit; DO NOT EDIT. 586 587 // Copyright ©2019 The Gonum Authors. All rights reserved. 588 // Use of this source code is governed by a BSD-style 589 // license that can be found in the LICENSE file. 590 591 package unit 592 593 import ( 594 "fmt" 595 "testing" 596 ) 597 598 func Test{{.DimensionName}}(t *testing.T) { 599 t.Parallel() 600 for _, value := range []float64{-1, 0, 1} { 601 var got {{.DimensionName}} 602 err := got.From({{.DimensionName}}(value).Unit()) 603 if err != nil { 604 t.Errorf("unexpected error for %T conversion: %v", got, err) 605 } 606 if got != {{.DimensionName}}(value) { 607 t.Errorf("unexpected result from round trip of %T(%v): got: %v want: %v", got, value, got, value) 608 } 609 if got != got.{{.DimensionName}}() { 610 t.Errorf("unexpected result from self interface method call: got: %#v want: %#v", got, value) 611 } 612 err = got.From(ether(1)) 613 if err == nil { 614 t.Errorf("expected error for ether to %T conversion", got) 615 } 616 } 617 } 618 619 func Test{{.DimensionName}}Format(t *testing.T) { 620 t.Parallel() 621 for _, test := range []struct{ 622 value {{.DimensionName}} 623 format string 624 want string 625 }{ 626 {1.23456789, "%v", "1.23456789{{with .PrintString}} {{.}}{{end}}"}, 627 {1.23456789, "%.1v", "1{{with .PrintString}} {{.}}{{end}}"}, 628 {1.23456789, "%20.1v", "{{if .PrintString}}{{$s := printf "1 %s" .PrintString}}{{printf "%20s" $s}}{{else}}{{printf "%20s" "1"}}{{end}}"}, 629 {1.23456789, "%20v", "{{if .PrintString}}{{$s := printf "1.23456789 %s" .PrintString}}{{printf "%20s" $s}}{{else}}{{printf "%20s" "1.23456789"}}{{end}}"}, 630 {1.23456789, "%1v", "1.23456789{{with .PrintString}} {{.}}{{end}}"}, 631 {1.23456789, "%#v", "unit.{{.DimensionName}}(1.23456789)"}, 632 {1.23456789, "%s", "%!s(unit.{{.DimensionName}}=1.23456789{{with .PrintString}} {{.}}{{end}})"}, 633 } { 634 got := fmt.Sprintf(test.format, test.value) 635 if got != test.want { 636 t.Errorf("Format %q %v: got: %q want: %q", test.format, test.value, got, test.want) 637 } 638 } 639 } 640 ` 641 642 var tests = template.Must(template.New("test").Parse(testTemplate)) 643 644 func generateTest(unit Unit) { 645 lowerName := strings.ToLower(unit.DimensionName) 646 filename := lowerName + "_test.go" 647 f, err := os.Create(filename) 648 if err != nil { 649 log.Fatal(err) 650 } 651 defer f.Close() 652 653 var buf bytes.Buffer 654 err = tests.Execute(&buf, unit) 655 if err != nil { 656 log.Fatal(err) 657 } 658 659 b, err := format.Source(buf.Bytes()) 660 if err != nil { 661 f.Write(buf.Bytes()) // This is here to debug bad format. 662 log.Fatalf("error formatting test for %q: %s", unit.DimensionName, err) 663 } 664 665 f.Write(b) 666 }