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!double)) 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!(double, "C"); 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!(BigInt, "bit"); 230 alias BinarySize = typeof(bit); 231 232 SymbolList!BigInt symbolList; 233 symbolList.addUnit("bit", bit); 234 symbolList.addPrefix("hob", 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 hobbit")); 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 value = parseFun(str); 338 catch 339 value = 1; 340 341 if (str.empty) 342 return RTQuantity!N(value, null); 343 344 auto input = str.to!string; 345 auto tokens = lex(input); 346 auto parser = QuantityParser!N(symbolList); 347 348 RTQuantity!N result = parser.parseCompoundUnit(tokens); 349 result.value *= value; 350 return result; 351 } 352 353 unittest // Test parsing 354 { 355 enum meter = unit!(double, "L"); 356 enum kilogram = unit!(double, "M"); 357 enum second = unit!(double, "T"); 358 enum one = meter / meter; 359 360 enum siSL = makeSymbolList!double( 361 withUnit("m", meter), 362 withUnit("kg", kilogram), 363 withUnit("s", second), 364 withPrefix("c", 0.01L), 365 withPrefix("m", 0.001L) 366 ); 367 368 static bool checkParse(Q)(string input, Q quantity) 369 { 370 return parseRTQuantity!(double, std.conv.parse!(double, string))(input, siSL) 371 == quantity.toRT; 372 } 373 374 assert(checkParse("1 m ", meter)); 375 assert(checkParse("1 mm", 0.001 * meter)); 376 assert(checkParse("1 m^-1", 1 / meter)); 377 assert(checkParse("1 m²", meter * meter)); 378 assert(checkParse("1 m⁺²", meter * meter)); 379 assert(checkParse("1 m⁻¹", 1 / meter)); 380 assert(checkParse("1 (m)", meter)); 381 assert(checkParse("1 (m^-1)", 1 / meter)); 382 assert(checkParse("1 ((m)^-1)^-1", meter)); 383 assert(checkParse("1 m*m", meter * meter)); 384 assert(checkParse("1 m m", meter * meter)); 385 assert(checkParse("1 m.m", meter * meter)); 386 assert(checkParse("1 m⋅m", meter * meter)); 387 assert(checkParse("1 m×m", meter * meter)); 388 assert(checkParse("1 m/m", meter / meter)); 389 assert(checkParse("1 m÷m", meter / meter)); 390 assert(checkParse("1 m.s", second * meter)); 391 assert(checkParse("1 m s", second * meter)); 392 assert(checkParse("1 m*m/m", meter)); 393 assert(checkParse("0.8", 0.8 * one)); 394 395 assertThrown!ParsingException(checkParse("1 c m", meter * meter)); 396 assertThrown!ParsingException(checkParse("1 c", 0.01 * meter)); 397 assertThrown!ParsingException(checkParse("1 Qm", meter)); 398 assertThrown!ParsingException(checkParse("1 m/", meter)); 399 assertThrown!ParsingException(checkParse("1 m^", meter)); 400 assertThrown!ParsingException(checkParse("1 m ) m", meter * meter)); 401 assertThrown!ParsingException(checkParse("1 m * m) m", meter * meter * meter)); 402 assertThrown!ParsingException(checkParse("1 m^²", meter * meter)); 403 assertThrown!ParsingException(checkParse("1-⁺⁵", one)); 404 } 405 406 // Holds a value and a dimensions for parsing 407 struct RTQuantity(N) 408 { 409 // The payload 410 N value; 411 412 // The dimensions of the quantity 413 int[string] dimensions; 414 } 415 416 // A parser that can parse a text for a unit or a quantity 417 struct QuantityParser(N) 418 { 419 alias RTQ = RTQuantity!N; 420 421 private SymbolList!N symbolList; 422 423 RTQ parseCompoundUnit(T)(auto ref T[] tokens, bool inParens = false) 424 if (is(T : Token)) 425 { 426 RTQ ret = parseExponentUnit(tokens); 427 if (tokens.empty || (inParens && tokens.front.type == Tok.rparen)) 428 return ret; 429 430 do { 431 tokens.check(); 432 auto cur = tokens.front; 433 434 bool multiply = true; 435 if (cur.type == Tok.div) 436 multiply = false; 437 438 if (cur.type == Tok.mul || cur.type == Tok.div) 439 { 440 tokens.advance(); 441 tokens.check(); 442 cur = tokens.front; 443 } 444 445 RTQ rhs = parseExponentUnit(tokens); 446 if (multiply) 447 { 448 ret.dimensions = ret.dimensions.binop!"*"(rhs.dimensions); 449 ret.value = ret.value * rhs.value; 450 } 451 else 452 { 453 ret.dimensions = ret.dimensions.binop!"/"(rhs.dimensions); 454 ret.value = ret.value / rhs.value; 455 } 456 457 if (tokens.empty || (inParens && tokens.front.type == Tok.rparen)) 458 break; 459 460 cur = tokens.front; 461 } 462 while (!tokens.empty); 463 464 return ret; 465 } 466 467 RTQ parseExponentUnit(T)(auto ref T[] tokens) 468 if (is(T : Token)) 469 { 470 RTQ ret = parseUnit(tokens); 471 472 if (tokens.empty) 473 return ret; 474 475 auto next = tokens.front; 476 if (next.type != Tok.exp && next.type != Tok.supinteger) 477 return ret; 478 479 if (next.type == Tok.exp) 480 tokens.advance(Tok.integer); 481 482 int n = parseInteger(tokens); 483 484 static if (__traits(compiles, std.math.pow(ret.value, n))) 485 ret.value = std.math.pow(ret.value, n); 486 else 487 foreach (i; 1 .. n) 488 ret.value *= ret.value; 489 ret.dimensions = ret.dimensions.exp(n); 490 return ret; 491 } 492 493 int parseInteger(T)(auto ref T[] tokens) 494 if (is(T : Token)) 495 { 496 tokens.check(Tok.integer, Tok.supinteger); 497 int n = tokens.front.integer; 498 if (tokens.length) 499 tokens.advance(); 500 return n; 501 } 502 503 RTQ parseUnit(T)(auto ref T[] tokens) 504 if (is(T : Token)) 505 { 506 RTQ ret; 507 508 if (tokens.front.type == Tok.lparen) 509 { 510 tokens.advance(); 511 ret = parseCompoundUnit(tokens, true); 512 tokens.check(Tok.rparen); 513 tokens.advance(); 514 } 515 else 516 ret = parsePrefixUnit(tokens); 517 518 return ret; 519 } 520 521 RTQ parsePrefixUnit(T)(auto ref T[] tokens) 522 if (is(T : Token)) 523 { 524 tokens.check(Tok.symbol); 525 auto str = tokens.front.slice; 526 if (tokens.length) 527 tokens.advance(); 528 529 // Try a standalone unit symbol (no prefix) 530 auto uptr = str in symbolList.units; 531 if (uptr) 532 return *uptr; 533 534 // Try with prefixes, the longest prefix first 535 N* factor; 536 for (size_t i = symbolList.maxPrefixLength; i > 0; i--) 537 { 538 if (str.length >= i) 539 { 540 string prefix = str[0 .. i].to!string; 541 factor = prefix in symbolList.prefixes; 542 if (factor) 543 { 544 string unit = str[i .. $].to!string; 545 enforceEx!ParsingException(unit.length, "Expecting a unit after the prefix " ~ prefix); 546 uptr = unit in symbolList.units; 547 if (uptr) 548 return RTQ(*factor * uptr.value, uptr.dimensions); 549 } 550 } 551 } 552 553 throw new ParsingException("Unknown unit symbol: '%s'".format(str)); 554 } 555 } 556 557 // Convert a compile-time quantity to its runtime equivalent. 558 auto toRT(Q)(Q quantity) 559 if (isQuantity!Q) 560 { 561 return RTQuantity!(Q.valueType)(quantity.rawValue, toAA!(Q.dimensions)); 562 } 563 564 enum Tok 565 { 566 none, 567 symbol, 568 mul, 569 div, 570 exp, 571 integer, 572 supinteger, 573 rparen, 574 lparen 575 } 576 577 struct Token 578 { 579 Tok type; 580 string slice; 581 int integer = int.max; 582 } 583 584 enum ctSupIntegerMap = [ 585 '⁰':'0', 586 '¹':'1', 587 '²':'2', 588 '³':'3', 589 '⁴':'4', 590 '⁵':'5', 591 '⁶':'6', 592 '⁷':'7', 593 '⁸':'8', 594 '⁹':'9', 595 '⁺':'+', 596 '⁻':'-' 597 ]; 598 static dchar[dchar] supIntegerMap; 599 static this() 600 { 601 supIntegerMap = ctSupIntegerMap; 602 } 603 604 Token[] lex(string input) @safe 605 { 606 enum State 607 { 608 none, 609 symbol, 610 integer, 611 supinteger 612 } 613 614 Token[] tokens; 615 auto tokapp = appender(tokens); // Only for runtime 616 617 void appendToken(Token token) 618 { 619 if (!__ctfe) 620 tokapp.put(token); 621 else 622 tokens ~= token; 623 } 624 625 auto original = input; 626 size_t i, j; 627 State state = State.none; 628 629 void pushToken(Tok type) 630 { 631 appendToken(Token(type, original[i .. j])); 632 i = j; 633 state = State.none; 634 } 635 636 void pushInteger(Tok type) 637 { 638 auto slice = original[i .. j]; 639 640 if (type == Tok.supinteger) 641 { 642 if (__ctfe) 643 slice = translate(slice, ctSupIntegerMap); 644 else 645 slice = translate(slice, supIntegerMap); 646 } 647 648 int n; 649 try 650 n = std.conv.parse!int(slice); 651 catch (Exception) 652 throw new ParsingException("Unexpected integer format: " ~ original[i .. j]); 653 654 enforceEx!ParsingException(slice.empty, "Unexpected integer format: " ~ slice); 655 656 appendToken(Token(type, original[i .. j], n)); 657 i = j; 658 state = State.none; 659 } 660 661 void push() 662 { 663 if (state == State.symbol) 664 pushToken(Tok.symbol); 665 else if (state == State.integer) 666 pushInteger(Tok.integer); 667 else if (state == State.supinteger) 668 pushInteger(Tok.supinteger); 669 } 670 671 while (!input.empty) 672 { 673 auto cur = input.front; 674 auto len = cur.codeLength!char; 675 switch (cur) 676 { 677 // Whitespace 678 case ' ': 679 case '\t': 680 case '\u00A0': 681 case '\u2000': .. case '\u200A': 682 case '\u202F': 683 case '\u205F': 684 push(); 685 j += len; 686 i = j; 687 break; 688 689 case '(': 690 push(); 691 j += len; 692 pushToken(Tok.lparen); 693 break; 694 695 case ')': 696 push(); 697 j += len; 698 pushToken(Tok.rparen); 699 break; 700 701 case '*': 702 case '.': 703 case '⋅': 704 case '×': 705 push(); 706 j += len; 707 pushToken(Tok.mul); 708 break; 709 710 case '/': 711 case '÷': 712 push(); 713 j += len; 714 pushToken(Tok.div); 715 break; 716 717 case '^': 718 push(); 719 j += len; 720 pushToken(Tok.exp); 721 break; 722 723 case '0': .. case '9': 724 case '-': 725 case '+': 726 if (state != State.integer) 727 push(); 728 state = State.integer; 729 j += len; 730 break; 731 732 case '⁰': 733 case '¹': 734 case '²': 735 case '³': 736 case '⁴': 737 case '⁵': 738 case '⁶': 739 case '⁷': 740 case '⁸': 741 case '⁹': 742 case '⁻': 743 case '⁺': 744 if (state != State.supinteger) 745 push(); 746 state = State.supinteger; 747 j += len; 748 break; 749 750 default: 751 if (state == State.integer || state == State.supinteger) 752 push(); 753 state = State.symbol; 754 j += len; 755 break; 756 } 757 input.popFront(); 758 } 759 push(); 760 761 if (!__ctfe) 762 return tokapp.data; 763 else 764 return tokens; 765 } 766 767 void advance(Types...)(ref Token[] tokens, Types types) 768 { 769 enforceEx!ParsingException(!tokens.empty, "Unexpected end of input"); 770 tokens.popFront(); 771 772 static if (Types.length) 773 check(tokens, types); 774 } 775 776 void check(Types...)(Token[] tokens, Types types) 777 { 778 enforceEx!ParsingException(!tokens.empty, "Unexpected end of input"); 779 auto token = tokens.front; 780 781 static if (Types.length) 782 { 783 bool ok = false; 784 Tok[] valid = [types]; 785 foreach (type; types) 786 { 787 if (token.type == type) 788 { 789 ok = true; 790 break; 791 } 792 } 793 import std.string : format; 794 enforceEx!ParsingException(ok, valid.length > 1 795 ? format("Found '%s' while expecting one of [%(%s, %)]", token.slice, valid) 796 : format("Found '%s' while expecting %s", token.slice, valid.front) 797 ); 798 } 799 } 800 801 // Mul or div two dimension arrays 802 int[string] binop(string op)(int[string] dim1, int[string] dim2) 803 { 804 static assert(op == "*" || op == "/", "Unsupported dimension operator: " ~ op); 805 806 int[string] result; 807 808 // Clone these dimensions in the result 809 if (__ctfe) 810 { 811 foreach (key; dim1.keys) 812 result[key] = dim1[key]; 813 } 814 else 815 result = dim1.dup; 816 817 // Merge the other dimensions 818 foreach (sym, pow; dim2) 819 { 820 enum powop = op == "*" ? "+" : "-"; 821 822 if (sym in dim1) 823 { 824 // A dimension is common between this one and the other: 825 // add or sub them 826 auto p = mixin("dim1[sym]" ~ powop ~ "pow"); 827 828 // If the power becomes 0, remove the dimension from the list 829 // otherwise, set the new power 830 if (p == 0) 831 result.remove(sym); 832 else 833 result[sym] = p; 834 } 835 else 836 { 837 // Add this new dimensions to the result 838 // (with a negative power if op == "/") 839 result[sym] = mixin(powop ~ "pow"); 840 } 841 } 842 843 return result; 844 } 845 846 // Raise a dimension array to a integer power (value) 847 int[string] exp(int[string] dim, int value) @safe pure 848 { 849 if (value == 0) 850 return null; 851 852 int[string] result; 853 foreach (sym, pow; dim) 854 result[sym] = pow * value; 855 return result; 856 } 857 858 // Raise a dimension array to a rational power (1/value) 859 int[string] expInv(int[string] dim, int value) @safe pure 860 { 861 assert(value > 0, "Bug: using Dimensions.expInv with a value <= 0"); 862 863 int[string] result; 864 foreach (sym, pow; dim) 865 { 866 enforce(pow % value == 0, "Operation results in a non-integral dimension"); 867 result[sym] = pow / value; 868 } 869 return result; 870 }