1 /*This program is free software: you can redistribute it and/or modify 2 it under the terms of the GNU General Public License as published by 3 the Free Software Foundation, either version 3 of the License, or 4 (at your option) any later version. 5 6 This program is distributed in the hope that it will be useful, 7 but WITHOUT ANY WARRANTY; without even the implied warranty of 8 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 GNU General Public License for more details. 10 11 You should have received a copy of the GNU General Public License 12 along with this program. If not, see <http://www.gnu.org/licenses/>.*/ 13 /** Copyright: 2022-2023, Ruby The Roobster*/ 14 /**Author: Ruby The Roobster, <michaeleverestc79@gmail.com>*/ 15 /**Date: January 16, 2023*/ 16 /** License: GPL-3.0*/ 17 18 /// Core part of the dutils math library. 19 module dutils.math.core; 20 21 public import dutils.math.def; 22 23 version(DLL) 24 { 25 mixin("export:"); 26 } 27 28 else 29 { 30 mixin("public:"); 31 } 32 33 /******************************************************** 34 * Registers a valid function that doesn't already exist. 35 * 36 * Params: 37 * name = 38 * The function name. 39 * func = 40 * The function parameters and return type. 41 * def = 42 * The definition of the function. 43 * 44 * Returns: 45 * Whether the function was registered. 46 */ 47 48 bool registerFunction(in dstring name, in dstring func, in dstring def) @safe 49 { 50 auto ret = validateFunction(func, def) && name ~ func !in funcList.funcs; 51 if(ret) 52 funcList.funcs[name ~ func] = def; 53 return ret; 54 } 55 56 /// 57 @safe unittest 58 { 59 dstring func = "(Number)(Number)"d; 60 dstring def = "x1"d; 61 dstring name = "f"d; 62 assert(registerFunction(name, func, def)); 63 assert(!registerFunction(name, func, def)); //No registering an already-existing function. 64 } 65 66 /************************************************** 67 * Removes a function provided that it exists. 68 * 69 * Params: 70 * name = 71 * The function name. 72 * func = 73 * The function parameters and return type. 74 * 75 * Returns: 76 * Whether the function exists or not. 77 */ 78 bool removeFunction(in dstring name, in dstring func) @safe 79 { 80 if(name ~ func in funcList.funcs) 81 { 82 funcList.funcs.remove(name ~ func); 83 return true; 84 } 85 return false; 86 } 87 88 /// 89 @safe unittest 90 { 91 dstring func = "(Number,Number)(Number)d"; 92 dstring def = "x1*x2"d; 93 dstring name = "f"d; 94 assert(registerFunction(name, func, def)); //Valid, a different function under the same name is a different function in the library's eyes. 95 assert(removeFunction(name, func)); 96 assert(!removeFunction(name, func)); //Cannot remove a non-existent function. 97 } 98 99 /******************************************* 100 * Validates a function. 101 * 102 * Params: 103 * func = 104 * The function name and parameters. 105 * def = 106 * The definition of the function. 107 * Returns: 108 * Whether the function is valid or not. 109 */ 110 111 bool validateFunction(in dstring func, in dstring def) @trusted 112 { 113 try 114 { 115 dstring[] params; 116 dstring[] returni; 117 size_t i = 0; 118 getParamsReturns(params, func, i); //Get the function return type. 119 dstring returns; 120 ++i; //Make sure to get out of the closing parenthesis. 121 getParamsReturns(returni, func, i); //Get the parameter types. 122 static foreach(type; typel) 123 { 124 mixin(type ~ "[] " ~ type ~ "ParamList;"); 125 mixin(type ~ "[] " ~ type ~ "OperandList;"); 126 } 127 returns = returni[0]; 128 //Make sure that we know the types of of each parameter. 129 dstring[] paramTypeList = []; 130 dstring returnType = null; 131 for(size_t j = 0; j < params.length; j++) 132 { 133 Switch: final switch(params[j]) 134 { 135 static foreach(type; typel) 136 { 137 case type: 138 mixin("++" ~ type ~ "ParamList.length;"); 139 ++paramTypeList.length; 140 paramTypeList[j] = type; 141 break Switch; 142 } 143 } 144 } 145 //Get the return type. 146 for(size_t j = 0; j < 1; j++) 147 { 148 Switch2: final switch(returns) 149 { 150 static foreach(type; typel) 151 { 152 case type: 153 returnType = type; 154 break Switch2; 155 } 156 } 157 } 158 //This gets scary. 159 //Buckle up. 160 bool isOperand = false; 161 bool isOp = false; 162 dstring currOperand; 163 dstring currOp; 164 dstring prevOp; 165 dstring tempNum; 166 import std.uni : isNumber; 167 i = 0; 168 long indentation = 0; 169 do 170 { 171 tempNum = ""d; 172 switch(def[i]) 173 { 174 case d('('): 175 ++indentation; 176 ++i; 177 break; 178 case d(')'): 179 --indentation; 180 ++i; 181 break; 182 case d('x'): 183 if(isOperand && !isOp) 184 return false; 185 prevOp = currOperand.idup; 186 isOperand = true; 187 ++i; 188 if(!def[i].isNumber) //Forced indexing of parameters. 189 return false; 190 do 191 { 192 tempNum ~= def[i]; 193 if(i == def.length-1) 194 { 195 if(def[i].isNumber) 196 break; 197 else 198 --i; 199 } 200 ++i; 201 } 202 while(def[i].isNumber); 203 import std.conv : to; 204 dstring tempType; 205 tempType = paramTypeList[to!size_t(tempNum) - 1]; 206 Switch3: final switch(tempType) 207 { 208 static foreach(type; typel) 209 { 210 case type: 211 mixin(type ~ "OperandList ~= new "d ~ type ~ "();"d); 212 currOperand = type; 213 break Switch3; 214 } 215 } 216 //Op verification. 217 if(isOp) //Speed on this gonna be O(n^2), where n is typel.keys.length, both compilation and runtime. 218 { 219 Switch4: final switch(currOperand) 220 { 221 static foreach(type; typel) 222 { 223 case type: 224 Switch5: final switch(prevOp) 225 { 226 static foreach(type2; typel) 227 { 228 case type2: 229 mixin("bool b = opCheckCrap(" ~ type2 ~ "OperandList[0], " 230 ~ type ~ "OperandList[0], currOp);"); 231 if(!b) 232 return false; 233 break Switch5; 234 } 235 } 236 break Switch4; 237 } 238 } 239 } 240 isOp = false; 241 if(i == def.length - 1) 242 { 243 if(!def[i].isNumber && def[i] != d(')')) 244 { 245 return false; 246 } 247 else if(def[i].isNumber) 248 ++i; 249 } 250 break; 251 case d('\\'): //May possibly be even worse than above, as it denotes a special operator. Also ridden with bugs, but I ain't fixin' that until this code is actually used. 252 if(isOperand && !isOp) 253 return false; 254 isOperand = true; 255 dstring opName; 256 prevOp = currOperand.idup; 257 ++i; 258 do 259 { 260 opName ~= def[i]; 261 ++i; 262 } 263 while(def[i] != d('(')); 264 //Get the type of the operand as it is needed for later. 265 dstring tempTypeCrap = ""d; 266 do 267 { 268 tempTypeCrap ~= def[i]; 269 ++i; 270 } 271 while(def[i] != d(')')); 272 ++i; 273 currOperand = tempTypeCrap; 274 if(def[i] != d('(')) 275 return false; 276 dstring[] tempOps = []; 277 ++i; 278 do 279 { 280 ++tempOps.length; 281 do 282 { 283 tempOps[$-1] ~= def[i]; 284 ++i; 285 } 286 while(def[i] != d(',') && def[i] != d(')')); //Just in case. 287 if(def[i] == d('(')) //Fix bug involving functions being broken 288 { 289 for(ubyte j = 0; j < 2; j++) 290 { 291 do 292 { 293 tempOps[$-1] ~= def[i]; 294 ++i; 295 } 296 while(def[i] != d(')')); 297 } 298 tempOps[$-1] ~= d(')'); 299 ++i; 300 } 301 if(def[i] != d(',') && def[i] != d(')')) 302 return false; 303 } 304 while(def[i] != d(')')); 305 ++i; 306 if(def[i] != '\\') 307 return false; 308 //Get the types of the parameters referenced by the special operator call. 309 dstring[] tempTypes = []; 310 dstring func2; 311 foreach(tempOp; tempOps) 312 { 313 size_t k = 1; 314 tempNum = ""d; 315 316 if(tempOp[0] == d('x')) 317 { 318 tempNum ~= d('x'); 319 do 320 { 321 tempNum ~= tempOp[k]; 322 ++k; 323 } 324 while(tempOp[k] != d('(') && k != tempOp.length - 1); 325 if(k == tempOp.length -1) 326 k = 1; 327 else 328 goto Func; 329 } 330 tempNum = ""; 331 func2 = "("d; 332 do 333 { 334 tempNum ~= tempOp[k]; 335 if(!tempOp[k].isNumber) 336 return false; 337 k++; 338 } 339 while(k != tempOp.length); 340 ++tempTypes.length; 341 import std.conv : to; 342 tempTypes[$-1] = paramTypeList[to!size_t(tempNum) - 1]; 343 continue; 344 345 Func: 346 k = 1; 347 //Function type header. 348 ++tempTypes.length; 349 tempTypes[$-1] = ""d; 350 //Get the function's return type (very easy, considering how it is specified). 351 do 352 { 353 tempNum ~= tempOp[k]; 354 ++k; 355 } 356 while(tempOp[k] != d(')')); 357 ++k; 358 tempTypes[$-1] = tempNum; 359 func2 ~= tempNum; 360 func2 ~= ")("d; 361 tempNum = ""d; 362 363 //Get the types of the function's parameters. 364 dstring[] tempTypes2 = []; 365 do 366 { 367 ++k; 368 ++tempTypes2.length; 369 do 370 { 371 tempTypes2[$-1] ~= tempOp[k]; 372 ++k; 373 } 374 while(tempOp[k] != d(',') && tempOp[k] != d(')')); 375 } 376 while(tempOp[k] != d(')')); 377 378 foreach(type; tempTypes2) 379 { 380 size_t l = 1; 381 if(type[0] != d('x')) 382 return false; 383 do 384 { 385 tempNum ~= type[l]; 386 if(!type[l].isNumber) 387 return false; 388 ++l; 389 } 390 while(l < type.length); 391 392 func2 ~= paramTypeList[to!size_t(tempNum) - 1]; 393 func2 ~= d(','); 394 } 395 396 --func2.length; 397 func2 ~= d(')'); 398 if(func2 !in funcList) 399 return false; 400 } 401 //Verify that the types match. 402 opName ~= "("d; 403 foreach(type; tempTypes) 404 { 405 opName ~= type; 406 opName ~= ","d; 407 } 408 --opName.length; 409 opName ~= ")"d; 410 if(opName !in opList) 411 return false; 412 currOperand = opName; 413 if(isOp) //Speed on this gonna be O(n^2), where n is typel.keys.length, both compilation and runtime. 414 { 415 Switch6: final switch(currOperand) 416 { 417 static foreach(type; typel) 418 { 419 case type: 420 Switch7: final switch(prevOp) 421 { 422 static foreach(type2; typel) 423 { 424 case type2: 425 mixin("bool b = opCheckCrap(" ~ type2 ~ "OperandList[0], " 426 ~ type ~ "OperandList[0], currOp);"); 427 if(!b) 428 return false; 429 break Switch7; 430 } 431 } 432 break Switch6; 433 } 434 } 435 ++i; 436 } 437 isOp = false; 438 break; 439 default: // Operators and functions 440 if(isOp) 441 return false; 442 isOp = true; 443 isOperand = false; 444 dchar[] tempstr = []; 445 do 446 { 447 tempstr ~= def[i]; 448 if(((def[i] != d('x')) && (def[i] != d('\\'))) && (def[i] != d('(') && def[i] != d(' '))) 449 ++i; 450 } 451 while((def[i] != d('x') && def[i] != d('\\')) && (def[i] != d('(') && def[i] != d(' '))); 452 453 /+if(def[i] != d('(')) // Operators+/ 454 currOp = tempstr.idup; 455 /+else // Oh shit oh fuck a function (THIS CODE DOESN'T WROK AND WILL BE FIXED LATER) 456 { 457 // We need to get the types of its arguments 458 tempstr = tempstr[0 .. $-1].dup; 459 dchar[] tempargs = []; 460 ++i; 461 do 462 { 463 tempargs ~= def[i]; 464 ++i; 465 } 466 while(def[i] != d(')')); 467 468 import std.algorithm; 469 auto indexes = tempargs.splitter(d(',')); 470 foreach(ref index; indexes) 471 index = index[1 .. $].dup; 472 473 size_t[] indices = []; 474 import std.conv; 475 foreach(index; indexes) 476 indices ~= index.to!size_t; 477 478 dstring[] temptypes; 479 foreach(indice; indices) 480 temptypes ~= paramTypeList[indice]; 481 482 483 }+/ 484 } 485 } 486 while(i < def.length); 487 if((isOp || !isOperand) || indentation != 0) //If there are no other syntax errors, ensure the following. 488 { 489 return false; 490 } 491 return true; 492 } 493 catch(Exception e) 494 { 495 return false; 496 } 497 } 498 499 /// 500 @safe unittest 501 { 502 /******************************************** 503 * List of stuff that is invalid but crashes: 504 * 505 * Whitespace 506 * Preceding Operators 507 * Invalid Operators and Characters 508 */ 509 dstring func = "(Number,Number)(Number)"d; 510 dstring def = "x1*x2"d; 511 assert(validateFunction(func, def)); 512 func = "(Number,Number,Number)(Number)"d; 513 def = "x1*x2+x3"d; 514 assert(validateFunction(func, def)); 515 def = "(x1"d; 516 assert(!validateFunction(func, def)); 517 def = "(x1)"d; 518 assert(validateFunction(func, def)); 519 def = "x1)"d; 520 assert(!validateFunction(func, def)); 521 def = "x1x2"d; 522 assert(!validateFunction(func, def)); 523 def = "x1+"d; 524 assert(!validateFunction(func, def)); 525 def = "x1*x2"d; 526 func = "(Number,Number)(Number)"d; 527 assert(registerFunction("f"d, func, def)); 528 //Functions within functions were too hard to implement, so we removed them. 529 //def = "x1* f(x1,x2)(Number)"d; 530 //assert(validateFunction(func, def)); 531 } 532 533 /************************************ 534 * Converts a char to a dchar. 535 * 536 * Params: 537 * c = 538 * The char to convert. 539 * Returns: 540 * The char converted to a dchar. 541 */ 542 package dchar d(char c) pure @safe 543 { 544 return cast(dchar)c; 545 } 546 547 private void getParamsReturns(ref dstring[] input, immutable dstring func, ref size_t i) pure @safe //Get the types of the function parameters and the return types. 548 in 549 { 550 assert(func[i] == d('(')); 551 } 552 do 553 { 554 ++i; 555 do 556 { 557 ++input.length; 558 do 559 { 560 input[$-1] ~= func[i]; 561 ++i; 562 } 563 while(func[i] != d(',') && func[i] != d(')')); 564 565 if(func[i] != d(')')) 566 ++i; 567 } 568 while(func[i] != d(')')); 569 } 570 571 //Function that checks whether using op currOp with type as its lhs and type2 as its rhs is valid. 572 private bool opCheckCrap(W, X)(W type, X type2, dstring currOp)//Please god let W and X be inferred from the arguments please. 573 { 574 return type.applyOp(currOp, type2); 575 } 576 577 ///We need the tuple type for executeFunction. 578 import std.typecons : Tuple; 579 580 /*********************************************************** 581 * Executes a function. 582 * 583 * 584 * Params: 585 * func = 586 * The function to execute. 587 * args = 588 * The function arguments, expressed as a tuple. 589 * precision = 590 * The precision of the returned Mtype. 591 * Returns: 592 * The result of calling the function. 593 */ 594 Return executeFunction(Return, Mtypes...)(in dstring func, in Tuple!(Mtypes) args, ulong precision = 18L) @safe 595 { 596 debug import std; 597 import std.uni : isNumber; 598 599 // Create temporary stores for each mtype. 600 static foreach(type; typel) 601 { 602 mixin(type ~ "[size_t][size_t]temp" ~ type ~ ";"); 603 } 604 dstring[size_t][size_t] parens; 605 parens[0] = [0 : ""d]; 606 // Parse the function 607 size_t indentation = 0; 608 size_t[size_t] parenNum; 609 parenNum[0] = 0; 610 for(size_t i = 0; i < funcList[func].length; ++i) // Organize the function into parentheses groups. 611 { 612 switch(funcList[func][i]) 613 { 614 case d('('): 615 ++indentation; 616 if(indentation !in parens) 617 { 618 parens[indentation] = [0 : ""d]; 619 parenNum[indentation] = 0; 620 } 621 break; 622 case d(')'): 623 ++parenNum[indentation]; 624 --indentation; 625 if(i+1 == funcList[func].length) 626 parens[indentation][parenNum[indentation]] ~= "()"d; 627 else if(funcList[func][i+1] == d(')')) 628 parens[indentation][parenNum[indentation]] ~= "()"d; 629 break; 630 case d('x'): 631 do 632 { 633 parens[indentation][parenNum[indentation]] ~= funcList[func][i]; 634 ++i; 635 if(i >= funcList[func].length) 636 break; 637 } 638 while(funcList[func][i].isNumber); 639 --i; 640 break; 641 case d('\\'): 642 do 643 { 644 parens[indentation][parenNum[indentation]] ~= funcList[func][i]; 645 ++i; 646 } 647 while(funcList[func][i] != d('\\')); 648 break; 649 default: 650 if(funcList[func][i-1] == d(')')) 651 parens[indentation][parenNum[indentation]] ~= "()"d; 652 parens[indentation][parenNum[indentation]] ~= funcList[func][i]; 653 } 654 } 655 //Sort the keys 656 auto keys = parens.keys; 657 import std.algorithm; 658 keys.sort!"b > a"; 659 size_t[][] keys2; 660 foreach(key; keys) 661 { 662 ++keys2.length; 663 keys2[$-1] = parens[key].keys.dup; 664 } 665 foreach(ref key; keys2) 666 key.sort!"b > a"; 667 foreach_reverse(key; keys) 668 { 669 debug import std.stdio; 670 size_t currParen = 0; 671 foreach(key2; keys2[key]) 672 { 673 dstring currOp = ""d; 674 dstring currType = ""d; 675 bool firstOperand = false; 676 for(size_t i = 0; i < parens[key][key2].length; i++) 677 { 678 //Get to work executing the function. 679 switch(parens[key][key2][i]) 680 { 681 case d('('): //Parentheses, also known as a pain in the ass. 682 static foreach(type; typel) 683 { 684 mixin("if(temp" ~ type ~ "[key+1][currParen] !is null) 685 { 686 currType = type; 687 if(!firstOperand) 688 { 689 temp" ~ type ~ "[key][key2] = new " ~ type ~ "(temp" ~ type ~ "[key+1][currParen].val); 690 } 691 }"); 692 } 693 if(firstOperand) 694 { 695 bool c; 696 static foreach(type; typel) 697 { 698 if(type == currType) 699 mixin("c = temp" ~ type ~ "[key][key2].applyOp(currOp, temp" ~ type ~ "[key+1][currParen]);"); 700 } 701 assert(c); 702 } 703 else 704 { 705 firstOperand = true; 706 } 707 ++i; 708 ++currParen; 709 currOp = ""d; 710 break; 711 case d('x'): //Input 712 ++i; 713 dstring tempIndex = ""d; 714 dstring tempType = ""d; 715 do 716 { 717 tempIndex ~= parens[key][key2][i]; 718 ++i; 719 if(i == parens[key][key2].length) 720 break; 721 } 722 while(parens[key][key2][i].isNumber); 723 724 static foreach(arg; 0 .. args.length) // Yes, this little fucker again. You'll be meeting him alot in this file. 725 { 726 if(arg + 1 == to!size_t(tempIndex)) // Generates YandereDev spaghetti code, there is no workaround for this. 727 tempType = Unconst!(typeof(args[arg])).stringof; 728 } 729 730 if(!firstOperand) 731 { 732 firstOperand = true; 733 static foreach(type; typel) 734 { 735 if(type == tempType) 736 { 737 static foreach(arg; 0 .. args.length) 738 { 739 if(arg + 1 == to!size_t(tempIndex)) 740 { 741 mixin("temp" ~ type ~ "[key][key2] = new " ~ type ~ "(args[arg].val);"); 742 } 743 } 744 } 745 } 746 currType = tempType; 747 } 748 else 749 { 750 bool c; 751 static foreach(type; typel) // O(x * y) Compile Time. D is the king of metaprogramming, but it's quite expensive. 752 { 753 if(type == currType) 754 { 755 static foreach(arg; 0 .. args.length) 756 { 757 if(arg + 1 == to!size_t(tempIndex)) 758 { 759 mixin("c = temp" ~ type ~ "[key][key2].applyOp(currOp, args[arg]);"); 760 } 761 } 762 } 763 } 764 assert(c); 765 } 766 --i; 767 currOp = ""d; 768 break; 769 case d('\\'): //Operators, such as derivatives, sums, and integrals. 770 break; 771 default: //Type specific operators. 772 do 773 { 774 currOp ~= parens[key][key2][i]; 775 ++i; 776 if(i == parens[key][key2].length) 777 break; 778 } 779 while(parens[key][key2][i] != d('\\') && parens[key][key2][i] != d('x') && parens[key][key2][i] 780 != d('(')); 781 --i; 782 } 783 } 784 } 785 } 786 static foreach(type; typel) 787 { 788 if(type == Return.stringof) 789 mixin("return temp" ~ type ~ "[0][0];"); 790 } 791 assert(0, "RubyTheRoobster sucks at programming... Please report this error."); 792 } 793 794 /// 795 @trusted unittest 796 { 797 Tuple!(Number, Number, Number) a; 798 a[0] = new Number(NumberContainer(BigInt(2), BigInt(0), 0L, 18UL)); 799 a[1] = new Number(NumberContainer(BigInt(3), BigInt(0), 0L, 18UL)); 800 a[2] = new Number(NumberContainer(BigInt(1), BigInt(0), 0L, 18UL)); 801 dstring func = "(Number,Number,Number)(Number)"d; 802 dstring def = "x1*x2*x3"d; 803 auto r = registerFunction("ree"d, func, def); 804 assert(r); 805 auto i = executeFunction!(Number, Number, Number, Number)("ree(Number,Number,Number)(Number)"d, a); 806 assert(i.toDstring == "6+0i"d, cast(char[])i.toDstring.dup); 807 assert(removeFunction("ree"d, func)); 808 def = "(x1*x2)*x3"d; 809 assert(registerFunction("ree"d, func, def)); 810 i = executeFunction!(Number, Number, Number, Number)("ree(Number,Number,Number)(Number)"d, a); 811 assert(i.toDstring == "6+0i"d, cast(char[])i.toDstring.dup); 812 // All of the above is working 813 def = "x1*(x2*x3)"d; 814 assert(removeFunction("ree"d, func)); 815 assert(registerFunction("ree"d, func, def)); 816 i = executeFunction!(Number, Number, Number, Number)("ree(Number,Number,Number)(Number)"d, a); 817 assert(i.toDstring == "6+0i"d, cast(char[])i.toDstring.dup); 818 def = "(x1*x2)*x3*x4"d; 819 assert(removeFunction("ree"d, func)); 820 func = "(Number,Number,Number,Number)(Number)"d; 821 assert(registerFunction("ree"d, func, def)); 822 Tuple!(Number, Number, Number, Number) b; 823 b[0] = new Number(NumberContainer(BigInt(2), BigInt(0), 0L, 18UL)); 824 b[1] = new Number(NumberContainer(BigInt(3), BigInt(0), 0L, 18UL)); 825 b[2] = new Number(NumberContainer(BigInt(1), BigInt(0), 0L, 18UL)); 826 b[3] = new Number(b[2].val); 827 i = executeFunction!(Number, Number, Number, Number, Number)("ree(Number,Number,Number,Number)(Number)"d, b); 828 assert(i.toDstring == "6+0i"d, cast(char[])i.toDstring.dup); 829 def = "x1*x2*(x3*x4)"d; 830 assert(removeFunction("ree"d, func)); 831 assert(registerFunction("ree"d, func, def)); 832 i = executeFunction!(Number, Number, Number, Number, Number)("ree(Number,Number,Number,Number)(Number)"d, b); 833 assert(i.toDstring == "6+0i"d, cast(char[])i.toDstring.dup); 834 def = "x1*(x2)*x3*(x4)"d; 835 assert(removeFunction("ree"d, func)); 836 assert(registerFunction("ree"d, func, def)); 837 i = executeFunction!(Number, Number, Number, Number, Number)("ree(Number,Number,Number,Number)(Number)"d, b); 838 assert(i.toDstring == "6+0i", cast(char[])i.toDstring.dup); 839 assert(removeFunction("ree"d, func)); 840 def = "((x1)*((x2)*(x3)))*x4"d; 841 assert(registerFunction("ree"d, func, def)); 842 i = executeFunction!(Number, Number, Number, Number, Number)("ree(Number,Number,Number,Number)(Number)"d, b); 843 assert(i.toDstring == "6+0i"d, cast(char[])i.toDstring.dup); 844 }