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