1 // Written in the D programming language 2 /++ 3 This module defines functions to parse units and quantities. The text 4 input is parsed according to the following grammar. For example: 5 $(DL 6 $(DT Prefixes and unit symbols must be joined:) 7 $(DD "1 mm" = 1 millimeter) 8 $(DD "1 m m" = 1 square meter) 9 $(BR) 10 $(DT Standalone units are preferred over prefixed ones:) 11 $(DD "1 cd" = 1 candela, not 1 centiday) 12 $(BR) 13 $(DT Powers of units:) 14 $(DD "1 m^2") 15 $(DD "1 m²" $(I (superscript integer))) 16 $(BR) 17 $(DT Multiplication of to units:) 18 $(DD "1 N m" $(I (whitespace))) 19 $(DD "1 N . m") 20 $(DD "1 N ⋅ m" $(I (centered dot))) 21 $(DD "1 N * m") 22 $(DD "1 N × m" $(I (times sign))) 23 $(BR) 24 $(DT Division of to units:) 25 $(DD "1 mol / s") 26 $(DD "1 mol ÷ s") 27 $(BR) 28 $(DT Grouping of units with parentheses:) 29 $(DD "1 kg/(m.s^2)" = 1 kg m⁻¹ s⁻²) 30 ) 31 32 Grammar: (whitespace not significant) 33 $(DL 34 $(DT Quantity:) 35 $(DD Units) 36 $(DD Number Units) 37 $(BR) 38 $(DT Number:) 39 $(DD $(I Numeric value parsed by std.conv.parse!real)) 40 $(BR) 41 $(DT Units:) 42 $(DD Unit) 43 $(DD Unit Units) 44 $(DD Unit Operator Units) 45 $(BR) 46 $(DT Operator:) 47 $(DD $(B *)) 48 $(DD $(B .)) 49 $(DD $(B ⋅)) 50 $(DD $(B ×)) 51 $(DD $(B /)) 52 $(DD $(B ÷)) 53 $(BR) 54 $(DT Unit:) 55 $(DD Base) 56 $(DD Base $(B ^) Integer) 57 $(DD Base SupInteger) 58 $(BR) 59 $(DT Base:) 60 $(DD Symbol) 61 $(DD Prefix Symbol) 62 $(DD $(B $(LPAREN)) Units $(B $(RPAREN))) 63 $(BR) 64 $(DT Symbol:) 65 $(DD $(I The symbol of a valid unit)) 66 $(BR) 67 $(DT Prefix:) 68 $(DD $(I The symbol of a valid prefix)) 69 $(BR) 70 $(DT Integer:) 71 $(DD $(I Integer value parsed by std.conv.parse!int)) 72 $(BR) 73 $(DT SupInteger:) 74 $(DD $(I Superscript version of Integer)) 75 ) 76 77 Copyright: Copyright 2013-2014, Nicolas Sicard 78 Authors: Nicolas Sicard 79 License: $(LINK www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 80 Source: $(LINK https://github.com/biozic/quantities) 81 +/ 82 module quantities.parsing; 83 84 import quantities.base; 85 import std.array; 86 import std.algorithm; 87 import std.conv; 88 import std.exception; 89 import std.math; 90 import std.range; 91 import std.string; 92 import std.traits; 93 import std.utf; 94 95 /++ 96 Contains the symbols of the units and the prefixes that a parser can handle. 97 +/ 98 struct SymbolList(N) 99 { 100 static assert(isNumberLike!N, "Incompatible type: " ~ N.stringof); 101 102 package 103 { 104 RTQuantity!N[string] units; 105 N[string] prefixes; 106 size_t maxPrefixLength; 107 } 108 109 /// Adds (or replaces) a unit in the list 110 void addUnit(Q)(string symbol, Q unit) 111 if (isQuantity!Q) 112 { 113 units[symbol] = unit.toRT; 114 } 115 116 /// Adds (or replaces) a prefix in the list 117 void addPrefix(N)(string symbol, N factor) 118 if (isNumberLike!N) 119 { 120 prefixes[symbol] = factor; 121 if (symbol.length > maxPrefixLength) 122 maxPrefixLength = symbol.length; 123 } 124 } 125 126 /++ 127 Helps build a SymbolList at compile-time. 128 129 Use with the global addUnit and addPrefix functions. 130 +/ 131 SymbolList!N makeSymbolList(N, Sym...)(Sym list) 132 { 133 SymbolList!N ret; 134 foreach (sym; list) 135 { 136 static if (is(typeof(sym) == WithUnit!Q, Q)) 137 { 138 static assert(is(Q.valueType : N), "Incompatible value types: %s and %s" 139 .format(Q.valueType.stringof, N.stringof)); 140 ret.units[sym.symbol] = sym.unit; 141 } 142 else static if (is(typeof(sym) == WithPrefix!T, T)) 143 { 144 static assert(is(T : N), "Incompatible value types: %s and %s" 145 .format(T.stringof, N.stringof)); 146 ret.prefixes[sym.symbol] = sym.factor; 147 if (sym.symbol.length > ret.maxPrefixLength) 148 ret.maxPrefixLength = sym.symbol.length; 149 } 150 else 151 static assert(false, "Unexpected symbol: " ~ sym.stringof); 152 } 153 return ret; 154 } 155 /// 156 unittest 157 { 158 enum euro = unit!("C", double); 159 alias Currency = typeof(euro); 160 enum dollar = 1.35 * euro; 161 162 enum symbolList = makeSymbolList!double( 163 withUnit("€", euro), 164 withUnit("$", dollar), 165 withPrefix("doz", 12) 166 ); 167 } 168 169 package struct WithUnit(Q) 170 { 171 string symbol; 172 RTQuantity!(Q.valueType) unit; 173 } 174 175 /// Creates a unit that can be added to a SymbolList via the SymbolList constuctor. 176 auto withUnit(Q)(string symbol, Q unit) 177 if (isQuantity!Q) 178 { 179 return WithUnit!Q(symbol, unit.toRT); 180 } 181 182 package struct WithPrefix(N) 183 { 184 string symbol; 185 N factor; 186 } 187 188 /// Creates a prefix that can be added to a SymbolList via the SymbolList constuctor. 189 auto withPrefix(N)(string symbol, N factor) 190 if (isNumberLike!N) 191 { 192 return WithPrefix!N(symbol, factor); 193 } 194 195 /++ 196 Creates a runtime parser capable of working on user-defined units and prefixes. 197 198 Params: 199 N = The type of the value type stored in the Quantity struct. 200 symbolList = A prefilled SymbolList struct that contains all units and prefixes. 201 parseFun = A function that can parse the beginning of a string to return a numeric value of type N. 202 After this function returns, it must have consumed the numeric part and leave only the unit part. 203 one = The value of type N that is equivalent to 1. 204 +/ 205 template rtQuantityParser( 206 N, 207 alias symbolList, 208 alias parseFun = (ref string s) => parse!N(s) 209 ) 210 { 211 auto rtQuantityParser(Q, S)(S str) 212 if (isQuantity!Q) 213 { 214 static assert(is(N : Q.valueType), "Incompatible value type: " ~ Q.valueType.stringof); 215 216 auto rtQuant = parseRTQuantity!(Q.valueType, parseFun)(str, symbolList); 217 enforceEx!DimensionException( 218 toAA!(Q.dimensions) == rtQuant.dimensions, 219 "Dimension error: [%s] is not compatible with [%s]" 220 .format(quantities.base.dimstr!(Q.dimensions), dimstr(rtQuant.dimensions))); 221 return Q.make(rtQuant.value); 222 } 223 } 224 /// 225 unittest 226 { 227 import std.bigint; 228 229 enum bit = unit!("bit", BigInt); 230 alias BinarySize = typeof(bit); 231 232 SymbolList!BigInt symbolList; 233 symbolList.addUnit("bit", bit); 234 symbolList.addPrefix("or", BigInt("1234567890987654321")); 235 236 static BigInt parseFun(ref string input) 237 { 238 import std.exception, std.regex; 239 enum rgx = ctRegex!`^(\d*)\s*(.*)$`; 240 auto m = enforce(match(input, rgx)); 241 input = m.captures[2]; 242 return BigInt(m.captures[1]); 243 } 244 245 alias parse = rtQuantityParser!(BigInt, symbolList, parseFun); 246 247 auto foo = BigInt("1234567890987654300") * bit; 248 foo += BigInt(21) * bit; 249 assert(foo == parse!BinarySize("1 orbit")); 250 } 251 252 /++ 253 Creates a compile-time parser capable of working on user-defined units and prefixes. 254 255 Contrary to a runtime parser, a compile-time parser infers the type of the parsed quantity 256 automatically from the dimensions of its components. 257 258 Params: 259 N = The type of the value type stored in the Quantity struct. 260 symbolList = A prefilled SymbolList struct that contains all units and prefixes. 261 parseFun = A function that can parse the beginning of a string to return a numeric value of type N. 262 After this function returns, it must have consumed the numeric part and leave only the unit part. 263 one = The value of type N that is equivalent to 1. 264 +/ 265 template ctQuantityParser( 266 N, 267 alias symbolList, 268 alias parseFun = (ref string s) => parse!N(s) 269 ) 270 { 271 template ctQuantityParser(string str) 272 { 273 static string dimTup(int[string] dims) 274 { 275 return dims.keys.map!(x => `"%s", %s`.format(x, dims[x])).join(", "); 276 } 277 278 // This is for a nice compile-time error message 279 enum msg = { return collectExceptionMsg(parseRTQuantity!(N, parseFun)(str, symbolList)); }(); 280 static if (msg) 281 { 282 static assert(false, msg); 283 } 284 else 285 { 286 enum q = parseRTQuantity!(N, parseFun)(str, symbolList); 287 enum dimStr = dimTup(q.dimensions); 288 mixin("alias dims = TypeTuple!(%s);".format(dimStr)); 289 enum ctQuantityParser = Quantity!(N, Sort!dims).make(q.value); 290 } 291 } 292 } 293 /// 294 version (D_Ddoc) // DMD BUG? (Differents symbolLists but same template instantiation) 295 unittest 296 { 297 enum bit = unit!("bit", ulong); 298 alias BinarySize = typeof(bit); 299 enum byte_ = 8 * bit; 300 301 enum symbolList = makeSymbolList!ulong( 302 withUnit("bit", bit), 303 withUnit("B", byte_), 304 withPrefix("hob", 7) 305 ); 306 307 alias sz = ctQuantityParser!(ulong, symbolList); 308 309 assert(sz!"1 hobbit".value(bit) == 7); 310 } 311 312 /// Exception thrown when parsing encounters an unexpected token. 313 class ParsingException : Exception 314 { 315 @safe pure nothrow 316 this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) 317 { 318 super(msg, file, line, next); 319 } 320 321 @safe pure nothrow 322 this(string msg, Throwable next, string file = __FILE__, size_t line = __LINE__) 323 { 324 super(msg, file, line, next); 325 } 326 } 327 328 package: 329 330 RTQuantity!N parseRTQuantity(N, alias parseFun, S, SL)(S str, auto ref SL symbolList) 331 { 332 static assert(isForwardRange!S && isSomeChar!(ElementType!S), 333 "input must be a forward range of a character type"); 334 335 N value; 336 try 337 { 338 value = parseFun(str); 339 } 340 catch 341 { 342 static if (isNumeric!N) 343 value = 1; 344 else static if (__traits(compiles, N(1))) 345 value = N(1); 346 else 347 static assert(false, "BUG"); 348 } 349 350 if (str.empty) 351 return RTQuantity!N(value, null); 352 353 auto input = str.to!string; 354 auto tokens = lex(input); 355 auto parser = QuantityParser!N(symbolList); 356 357 RTQuantity!N result = parser.parseCompoundUnit(tokens); 358 result.value *= value; 359 return result; 360 } 361 362 unittest // Test parsing 363 { 364 enum meter = unit!"L"; 365 enum kilogram = unit!"M"; 366 enum second = unit!"T"; 367 enum one = meter / meter; 368 369 enum siSL = makeSymbolList!real( 370 withUnit("m", meter), 371 withUnit("kg", kilogram), 372 withUnit("s", second), 373 withPrefix("c", 0.01L), 374 withPrefix("m", 0.001L) 375 ); 376 377 static bool checkParse(Q)(string input, Q quantity) 378 { 379 return parseRTQuantity!(real, std.conv.parse!(real, string))(input, siSL) 380 == quantity.toRT; 381 } 382 383 assert(checkParse("1 m ", meter)); 384 assert(checkParse("1 mm", 0.001L * meter)); 385 assert(checkParse("1 m^-1", 1 / meter)); 386 assert(checkParse("1 m²", meter * meter)); 387 assert(checkParse("1 m⁺²", meter * meter)); 388 assert(checkParse("1 m⁻¹", 1 / meter)); 389 assert(checkParse("1 (m)", meter)); 390 assert(checkParse("1 (m^-1)", 1 / meter)); 391 assert(checkParse("1 ((m)^-1)^-1", meter)); 392 assert(checkParse("1 m*m", meter * meter)); 393 assert(checkParse("1 m m", meter * meter)); 394 assert(checkParse("1 m.m", meter * meter)); 395 assert(checkParse("1 m⋅m", meter * meter)); 396 assert(checkParse("1 m×m", meter * meter)); 397 assert(checkParse("1 m/m", meter / meter)); 398 assert(checkParse("1 m÷m", meter / meter)); 399 assert(checkParse("1 m.s", second * meter)); 400 assert(checkParse("1 m s", second * meter)); 401 assert(checkParse("1 m*m/m", meter)); 402 assert(checkParse("0.8", 0.8L * one)); 403 404 assertThrown!ParsingException(checkParse("1 c m", meter * meter)); 405 assertThrown!ParsingException(checkParse("1 c", 0.01L * meter)); 406 assertThrown!ParsingException(checkParse("1 Qm", meter)); 407 assertThrown!ParsingException(checkParse("1 m/", meter)); 408 assertThrown!ParsingException(checkParse("1 m^", meter)); 409 assertThrown!ParsingException(checkParse("1 m ) m", meter * meter)); 410 assertThrown!ParsingException(checkParse("1 m * m) m", meter * meter * meter)); 411 assertThrown!ParsingException(checkParse("1 m^²", meter * meter)); 412 assertThrown!ParsingException(checkParse("1-⁺⁵", one)); 413 } 414 415 // Holds a value and a dimensions for parsing 416 struct RTQuantity(N) 417 { 418 // The payload 419 N value; 420 421 // The dimensions of the quantity 422 int[string] dimensions; 423 } 424 425 // A parser that can parse a text for a unit or a quantity 426 struct QuantityParser(N) 427 { 428 alias RTQ = RTQuantity!N; 429 430 private SymbolList!N symbolList; 431 432 RTQ parseCompoundUnit(T)(auto ref T[] tokens, bool inParens = false) 433 if (is(T : Token)) 434 { 435 RTQ ret = parseExponentUnit(tokens); 436 if (tokens.empty || (inParens && tokens.front.type == Tok.rparen)) 437 return ret; 438 439 do { 440 tokens.check(); 441 auto cur = tokens.front; 442 443 bool multiply = true; 444 if (cur.type == Tok.div) 445 multiply = false; 446 447 if (cur.type == Tok.mul || cur.type == Tok.div) 448 { 449 tokens.advance(); 450 tokens.check(); 451 cur = tokens.front; 452 } 453 454 RTQ rhs = parseExponentUnit(tokens); 455 if (multiply) 456 { 457 ret.dimensions = ret.dimensions.binop!"*"(rhs.dimensions); 458 ret.value = ret.value * rhs.value; 459 } 460 else 461 { 462 ret.dimensions = ret.dimensions.binop!"/"(rhs.dimensions); 463 ret.value = ret.value / rhs.value; 464 } 465 466 if (tokens.empty || (inParens && tokens.front.type == Tok.rparen)) 467 break; 468 469 cur = tokens.front; 470 } 471 while (!tokens.empty); 472 473 return ret; 474 } 475 476 RTQ parseExponentUnit(T)(auto ref T[] tokens) 477 if (is(T : Token)) 478 { 479 RTQ ret = parseUnit(tokens); 480 481 if (tokens.empty) 482 return ret; 483 484 auto next = tokens.front; 485 if (next.type != Tok.exp && next.type != Tok.supinteger) 486 return ret; 487 488 if (next.type == Tok.exp) 489 tokens.advance(Tok.integer); 490 491 int n = parseInteger(tokens); 492 493 static if (__traits(compiles, std.math.pow(ret.value, n))) 494 ret.value = std.math.pow(ret.value, n); 495 else 496 foreach (i; 1 .. n) 497 ret.value *= ret.value; 498 ret.dimensions = ret.dimensions.exp(n); 499 return ret; 500 } 501 502 int parseInteger(T)(auto ref T[] tokens) 503 if (is(T : Token)) 504 { 505 tokens.check(Tok.integer, Tok.supinteger); 506 int n = tokens.front.integer; 507 if (tokens.length) 508 tokens.advance(); 509 return n; 510 } 511 512 RTQ parseUnit(T)(auto ref T[] tokens) 513 if (is(T : Token)) 514 { 515 RTQ ret; 516 517 if (tokens.front.type == Tok.lparen) 518 { 519 tokens.advance(); 520 ret = parseCompoundUnit(tokens, true); 521 tokens.check(Tok.rparen); 522 tokens.advance(); 523 } 524 else 525 ret = parsePrefixUnit(tokens); 526 527 return ret; 528 } 529 530 RTQ parsePrefixUnit(T)(auto ref T[] tokens) 531 if (is(T : Token)) 532 { 533 tokens.check(Tok.symbol); 534 auto str = tokens.front.slice; 535 if (tokens.length) 536 tokens.advance(); 537 538 // Try a standalone unit symbol (no prefix) 539 auto uptr = str in symbolList.units; 540 if (uptr) 541 return *uptr; 542 543 // Try with prefixes, the longest prefix first 544 N* factor; 545 for (size_t i = symbolList.maxPrefixLength; i > 0; i--) 546 { 547 if (str.length >= i) 548 { 549 string prefix = str[0 .. i].to!string; 550 factor = prefix in symbolList.prefixes; 551 if (factor) 552 { 553 string unit = str[i .. $].to!string; 554 enforceEx!ParsingException(unit.length, "Expecting a unit after the prefix " ~ prefix); 555 uptr = unit in symbolList.units; 556 if (uptr) 557 return RTQ(*factor * uptr.value, uptr.dimensions); 558 } 559 } 560 } 561 562 throw new ParsingException("Unknown unit symbol: '%s'".format(str)); 563 } 564 } 565 566 // Convert a compile-time quantity to its runtime equivalent. 567 auto toRT(Q)(Q quantity) 568 if (isQuantity!Q) 569 { 570 return RTQuantity!(Q.valueType)(quantity.rawValue, toAA!(Q.dimensions)); 571 } 572 573 enum Tok 574 { 575 none, 576 symbol, 577 mul, 578 div, 579 exp, 580 integer, 581 supinteger, 582 rparen, 583 lparen 584 } 585 586 struct Token 587 { 588 Tok type; 589 string slice; 590 int integer = int.max; 591 } 592 593 enum ctSupIntegerMap = [ 594 '⁰':'0', 595 '¹':'1', 596 '²':'2', 597 '³':'3', 598 '⁴':'4', 599 '⁵':'5', 600 '⁶':'6', 601 '⁷':'7', 602 '⁸':'8', 603 '⁹':'9', 604 '⁺':'+', 605 '⁻':'-' 606 ]; 607 static __gshared dchar[dchar] supIntegerMap; 608 shared static this() 609 { 610 supIntegerMap = ctSupIntegerMap; 611 } 612 613 Token[] lex(string input) 614 { 615 enum State 616 { 617 none, 618 symbol, 619 integer, 620 supinteger 621 } 622 623 Token[] tokens; 624 if (!__ctfe) 625 tokens.reserve(input.length); 626 627 auto original = input; 628 size_t i, j; 629 State state = State.none; 630 631 void pushToken(Tok type) 632 { 633 tokens ~= Token(type, original[i .. j]); 634 i = j; 635 state = State.none; 636 } 637 638 void pushInteger(Tok type) 639 { 640 auto slice = original[i .. j]; 641 642 if (type == Tok.supinteger) 643 { 644 if (__ctfe) 645 slice = translate(slice, ctSupIntegerMap); 646 else 647 slice = translate(slice, supIntegerMap); 648 } 649 650 int n; 651 try 652 n = std.conv.parse!int(slice); 653 catch (Exception) 654 throw new ParsingException("Unexpected integer format: " ~ original[i .. j]); 655 656 enforceEx!ParsingException(slice.empty, "Unexpected integer format: " ~ slice); 657 658 tokens ~= Token(type, original[i .. j], n); 659 i = j; 660 state = State.none; 661 } 662 663 void push() 664 { 665 if (state == State.symbol) 666 pushToken(Tok.symbol); 667 else if (state == State.integer) 668 pushInteger(Tok.integer); 669 else if (state == State.supinteger) 670 pushInteger(Tok.supinteger); 671 } 672 673 while (!input.empty) 674 { 675 auto cur = input.front; 676 auto len = cur.codeLength!char; 677 switch (cur) 678 { 679 // Whitespace 680 case ' ': 681 case '\t': 682 case '\u00A0': 683 case '\u2000': .. case '\u200A': 684 case '\u202F': 685 case '\u205F': 686 push(); 687 j += len; 688 i = j; 689 break; 690 691 case '(': 692 push(); 693 j += len; 694 pushToken(Tok.lparen); 695 break; 696 697 case ')': 698 push(); 699 j += len; 700 pushToken(Tok.rparen); 701 break; 702 703 case '*': 704 case '.': 705 case '⋅': 706 case '×': 707 push(); 708 j += len; 709 pushToken(Tok.mul); 710 break; 711 712 case '/': 713 case '÷': 714 push(); 715 j += len; 716 pushToken(Tok.div); 717 break; 718 719 case '^': 720 push(); 721 j += len; 722 pushToken(Tok.exp); 723 break; 724 725 case '0': .. case '9': 726 case '-': 727 case '+': 728 if (state != State.integer) 729 push(); 730 state = State.integer; 731 j += len; 732 break; 733 734 case '⁰': 735 case '¹': 736 case '²': 737 case '³': 738 case '⁴': 739 case '⁵': 740 case '⁶': 741 case '⁷': 742 case '⁸': 743 case '⁹': 744 case '⁻': 745 case '⁺': 746 if (state != State.supinteger) 747 push(); 748 state = State.supinteger; 749 j += len; 750 break; 751 752 default: 753 if (state == State.integer || state == State.supinteger) 754 push(); 755 state = State.symbol; 756 j += len; 757 break; 758 } 759 input.popFront(); 760 } 761 push(); 762 return tokens; 763 } 764 765 void advance(Types...)(ref Token[] tokens, Types types) 766 { 767 enforceEx!ParsingException(!tokens.empty, "Unexpected end of input"); 768 tokens.popFront(); 769 770 static if (Types.length) 771 check(tokens, types); 772 } 773 774 void check(Types...)(Token[] tokens, Types types) 775 { 776 enforceEx!ParsingException(!tokens.empty, "Unexpected end of input"); 777 auto token = tokens.front; 778 779 static if (Types.length) 780 { 781 bool ok = false; 782 Tok[] valid = [types]; 783 foreach (type; types) 784 { 785 if (token.type == type) 786 { 787 ok = true; 788 break; 789 } 790 } 791 import std.string : format; 792 enforceEx!ParsingException(ok, valid.length > 1 793 ? format("Found '%s' while expecting one of [%(%s, %)]", token.slice, valid) 794 : format("Found '%s' while expecting %s", token.slice, valid.front) 795 ); 796 } 797 } 798 799 // Mul or div two dimension arrays 800 int[string] binop(string op)(int[string] dim1, int[string] dim2) 801 { 802 static assert(op == "*" || op == "/", "Unsupported dimension operator: " ~ op); 803 804 int[string] result; 805 806 // Clone these dimensions in the result 807 if (__ctfe) 808 { 809 foreach (key; dim1.keys) 810 result[key] = dim1[key]; 811 } 812 else 813 result = dim1.dup; 814 815 // Merge the other dimensions 816 foreach (sym, pow; dim2) 817 { 818 enum powop = op == "*" ? "+" : "-"; 819 820 if (sym in dim1) 821 { 822 // A dimension is common between this one and the other: 823 // add or sub them 824 auto p = mixin("dim1[sym]" ~ powop ~ "pow"); 825 826 // If the power becomes 0, remove the dimension from the list 827 // otherwise, set the new power 828 if (p == 0) 829 result.remove(sym); 830 else 831 result[sym] = p; 832 } 833 else 834 { 835 // Add this new dimensions to the result 836 // (with a negative power if op == "/") 837 result[sym] = mixin(powop ~ "pow"); 838 } 839 } 840 841 return result; 842 } 843 844 // Raise a dimension array to a integer power (value) 845 int[string] exp(int[string] dim, int value) 846 { 847 if (value == 0) 848 return null; 849 850 int[string] result; 851 foreach (sym, pow; dim) 852 result[sym] = pow * value; 853 return result; 854 } 855 856 // Raise a dimension array to a rational power (1/value) 857 int[string] expInv(int[string] dim, int value) 858 { 859 assert(value > 0, "Bug: using Dimensions.expInv with a value <= 0"); 860 861 int[string] result; 862 foreach (sym, pow; dim) 863 { 864 enforce(pow % value == 0, "Operation results in a non-integral dimension"); 865 result[sym] = pow / value; 866 } 867 return result; 868 } 869 870 // Returns the string representation of a dimension array 871 string dimstr(int[string] dim) 872 { 873 import std.algorithm : filter; 874 import std.array : join; 875 import std.conv : to; 876 877 static string stringize(string base, int power) 878 { 879 if (power == 0) 880 return null; 881 if (power == 1) 882 return base; 883 return base ~ "^" ~ to!string(power); 884 } 885 886 string[] dimstrs; 887 foreach (sym, pow; dim) 888 dimstrs ~= stringize(sym, pow); 889 890 return "%-(%s %)".format(dimstrs.filter!"a !is null"); 891 } 892 893