1/* 2 * ***************************************************************************** 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 * 6 * Copyright (c) 2018-2025 Gavin D. Howard and contributors. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions are met: 10 * 11 * * Redistributions of source code must retain the above copyright notice, this 12 * list of conditions and the following disclaimer. 13 * 14 * * Redistributions in binary form must reproduce the above copyright notice, 15 * this list of conditions and the following disclaimer in the documentation 16 * and/or other materials provided with the distribution. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 22 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 * POSSIBILITY OF SUCH DAMAGE. 29 * 30 * ***************************************************************************** 31 * 32 * The build package file. 33 * 34 */ 35 36/// The path to the safe install script. 37SAFE_INSTALL: str = path.join(src_dir, "scripts/safe-install.sh"); 38 39/// The file mode for executables, as an argument to the safe install script. 40EXEC_INSTALL_MODE: str = "-Dm755"; 41 42/// The file mode for man pages and other files, as an argument to the safe 43/// install script. 44MANPAGE_INSTALL_MODE: str = "-Dm644"; 45 46// Save this. 47OS: str = platform.os; 48 49DESTDIR: str = str(config["destdir"]); 50 51EXECPREFIX: str = str(config["execprefix"]); 52EXECSUFFIX: str = str(config["execsuffix"]); 53 54/** 55 * Generates the true executable name for the given base name. 56 * @param name The base name of the executable. 57 * @return The true name of the executable, including prefix, suffix, and 58 extension. 59 */ 60fn exe_name(name: str) -> str 61{ 62 temp: str = EXECPREFIX +~ name +~ EXECSUFFIX; 63 return if OS == "Windows" { temp +~ ".exe"; } else { temp; }; 64} 65 66/** 67 * Generates the default executable name for the given base name. 68 * @param name The base name of the executable. 69 * @return The true name of the executable, including prefix, suffix, and 70 extension. 71 */ 72fn default_exe_name(name: str) -> str 73{ 74 return if OS == "Windows" { name +~ ".exe"; } else { name; }; 75} 76 77/** 78 * Generates the true library name for the given base name. 79 * @param name The base name of the library. 80 * @return The true name of the library, including prefix and extension. 81 */ 82fn lib_name(name: str) -> str 83{ 84 ext: str = if OS == "Windows" { ".lib"; } else { ".a"; }; 85 return "lib" +~ name +~ ext; 86} 87 88BC_BIN: str = exe_name("bc"); 89DC_BIN: str = exe_name("dc"); 90LIBRARY: str = lib_name("libbcl"); 91 92BC_MANPAGE: str = EXECPREFIX +~ "bc" +~ EXECSUFFIX +~ ".1"; 93DC_MANPAGE: str = EXECPREFIX +~ "dc" +~ EXECSUFFIX +~ ".1"; 94BCL_MANPAGE: str = "bcl.3"; 95 96BCL_HEADER: str = "bcl.h"; 97BCL_HEADER_PATH: str = path.join(src_dir, path.join("include", BCL_HEADER)); 98PC_FILE: str = "bcl.pc"; 99 100/** 101 * Returns the string value of the define for a prompt default define for an 102 * executable. 103 * @param name The base name of the executable. 104 * @return The string value of the compiler define for the prompt default. 105 */ 106fn prompt(name: str) -> str 107{ 108 opt: sym = sym(config[name +~ "/default_prompt"]); 109 110 ret: str = 111 if opt == @off 112 { 113 "0"; 114 } 115 else if opt == @tty_mode 116 { 117 str(uint(bool(config[name +~ "/default_tty_mode"]))); 118 } 119 else 120 { 121 "1"; 122 }; 123 124 return ret; 125} 126 127HEADERS: []str = find_src_ext("include", "h"); 128 129FORCE: bool = bool(config["force"]); 130 131BUILD_MODE: sym = sym(config["build_mode"]); 132 133BC_ENABLED: str = str(uint(BUILD_MODE == @both || BUILD_MODE == @bc)); 134DC_ENABLED: str = str(uint(BUILD_MODE == @both || BUILD_MODE == @dc)); 135LIBRARY_ENABLED: str = str(uint(BUILD_MODE == @library)); 136 137EXTRA_MATH_ENABLED: str = str(uint(bool(config["extra_math"]))); 138 139HISTORY: sym = sym(config["history"]); 140HISTORY_ENABLED: str = str(uint(HISTORY != @none)); 141EDITLINE_ENABLED: str = str(uint(HISTORY == @editline)); 142READLINE_ENABLED: str = str(uint(HISTORY == @readline)); 143 144NLS_ENABLED: str = 145if OS == "Windows" || BUILD_MODE == @library 146{ 147 "0"; 148} 149else 150{ 151 str(uint(sym(config["locales"]) != @none)); 152}; 153 154BUILD_TYPE: str = 155if EXTRA_MATH_ENABLED != "0" && HISTORY_ENABLED != "0" && NLS_ENABLED != "0" 156{ 157 "A"; 158} 159else 160{ 161 t: str = if EXTRA_MATH_ENABLED != "0" { ""; } else { "E"; } +~ 162 if HISTORY_ENABLED != "0" { ""; } else { "H"; } +~ 163 if NLS_ENABLED != "0" { ""; } else { "N"; }; 164 165 t; 166}; 167 168OPTIMIZE: str = str(config["optimization"]); 169 170VALGRIND_ARGS: []str = @[ 171 "valgrind", 172 "--error-exitcode=100", 173 "--leak-check=full", 174 "--show-leak-kinds=all", 175 "--errors-for-leak-kinds=all", 176 "--track-fds=yes", 177 "--track-origins=yes", 178]; 179 180// Get the compiler. The user might have set one at the command line. 181CC: str = language.compiler; 182 183// Set optimization to "0" if it is empty. 184CFLAGS_OPT: str = if OPTIMIZE == "" { "0"; } else { OPTIMIZE; }; 185 186// Get the command-line option for defining a preprocessor variable. 187DEFOPT: str = compiler_db["opt.define"]; 188 189// Get the command-line string for the optimization option for the compiler. 190OPTOPT: str = compiler_db["opt.optimization"] +~ CFLAGS_OPT; 191 192// Get the compiler option for the object file to output to. 193OBJOUTOPT: str = compiler_db["opt.objout"]; 194EXEOUTOPT: str = compiler_db["opt.exeout"]; 195 196// Get the compiler option for outputting an object file rather than an 197// executable. 198OBJOPT: str = compiler_db["opt.obj"]; 199 200// Get the compiler option for setting an include directory. 201INCOPT: str = compiler_db["opt.include"] +~ path.join(src_dir, "include"); 202 203COVERAGE_CFLAGS: []str = 204if bool(config["coverage"]) 205{ 206 @[ "-fprofile-arcs", "-ftest-coverage", "-g", "-O0", DEFOPT +~ "NDEBUG" ]; 207}; 208 209MAINEXEC: str = 210if BUILD_MODE == @both || BUILD_MODE == @bc || BUILD_MODE == @library 211{ 212 BC_BIN; 213} 214else 215{ 216 DC_BIN; 217}; 218 219MAINEXEC_FLAGS: []str = @[ DEFOPT +~ "MAINEXEC=" +~ MAINEXEC ]; 220 221// XXX: Library needs these defines to be true. 222BC_DEF: str = if LIBRARY_ENABLED == "0" { BC_ENABLED; } else { "1"; }; 223DC_DEF: str = if LIBRARY_ENABLED == "0" { DC_ENABLED; } else { "1"; }; 224 225CFLAGS1: []str = config_list["cflags"] +~ @[ OPTOPT, INCOPT ] +~ 226 COVERAGE_CFLAGS +~ MAINEXEC_FLAGS; 227CFLAGS2: []str = @[ 228 DEFOPT +~ "BC_ENABLED=" +~ BC_DEF, 229 DEFOPT +~ "DC_ENABLED=" +~ DC_DEF, 230 DEFOPT +~ "BUILD_TYPE=" +~ BUILD_TYPE, 231 DEFOPT +~ "EXECPREFIX=" +~ str(config["execprefix"]), 232 DEFOPT +~ "BC_NUM_KARATSUBA_LEN=" +~ str(num(config["karatsuba_len"])), 233 DEFOPT +~ "BC_ENABLE_LIBRARY=" +~ LIBRARY_ENABLED, 234 DEFOPT +~ "BC_ENABLE_NLS=" +~ NLS_ENABLED, 235 DEFOPT +~ "BC_ENABLE_EXTRA_MATH=" +~ EXTRA_MATH_ENABLED, 236 DEFOPT +~ "BC_ENABLE_HISTORY=" +~ HISTORY_ENABLED, 237 DEFOPT +~ "BC_ENABLE_EDITLINE=" +~ EDITLINE_ENABLED, 238 DEFOPT +~ "BC_ENABLE_READLINE=" +~ READLINE_ENABLED, 239 DEFOPT +~ "BC_ENABLE_MEMCHECK=" +~ str(uint(bool(config["memcheck"]))), 240 DEFOPT +~ "BC_ENABLE_AFL=" +~ str(uint(bool(config["afl"]))), 241 DEFOPT +~ "BC_ENABLE_OSSFUZZ=" +~ str(uint(bool(config["ossfuzz"]))), 242 DEFOPT +~ "BC_DEFAULT_BANNER=" +~ 243 str(uint(bool(config["bc/default_banner"]))), 244 DEFOPT +~ "BC_DEFAULT_SIGINT_RESET=" +~ 245 str(uint(bool(config["bc/default_sigint_reset"]))), 246 DEFOPT +~ "BC_DEFAULT_TTY_MODE=" +~ 247 str(uint(bool(config["bc/default_tty_mode"]))), 248 DEFOPT +~ "BC_DEFAULT_PROMPT=" +~ prompt("bc"), 249 DEFOPT +~ "BC_DEFAULT_EXPR_EXIT=" +~ 250 str(uint(bool(config["bc/default_expr_exit"]))), 251 DEFOPT +~ "BC_DEFAULT_DIGIT_CLAMP=" +~ 252 str(uint(bool(config["bc/default_digit_clamp"]))), 253 DEFOPT +~ "DC_DEFAULT_SIGINT_RESET=" +~ 254 str(uint(bool(config["dc/default_sigint_reset"]))), 255 DEFOPT +~ "DC_DEFAULT_TTY_MODE=" +~ 256 str(uint(bool(config["dc/default_tty_mode"]))), 257 DEFOPT +~ "DC_DEFAULT_PROMPT=" +~ prompt("dc"), 258 DEFOPT +~ "DC_DEFAULT_EXPR_EXIT=" +~ 259 str(uint(bool(config["dc/default_expr_exit"]))), 260 DEFOPT +~ "DC_DEFAULT_DIGIT_CLAMP=" +~ 261 str(uint(bool(config["dc/default_digit_clamp"]))), 262]; 263CFLAGS: []str = CFLAGS1 +~ CFLAGS2; 264 265LDFLAGS: []str = config_list["ldflags"]; 266 267COMMON_C_FILES: []str = @[ 268 "src/data.c", 269 "src/num.c", 270 "src/rand.c", 271 "src/vector.c", 272 "src/vm.c", 273]; 274 275EXEC_C_FILES: []str = @[ 276 "src/args.c", 277 "src/file.c", 278 "src/lang.c", 279 "src/lex.c", 280 "src/main.c", 281 "src/opt.c", 282 "src/parse.c", 283 "src/program.c", 284 "src/read.c", 285]; 286 287BC_C_FILES: []str = @[ 288 "src/bc.c", 289 "src/bc_lex.c", 290 "src/bc_parse.c", 291]; 292 293DC_C_FILES: []str = @[ 294 "src/dc.c", 295 "src/dc_lex.c", 296 "src/dc_parse.c", 297]; 298 299HISTORY_C_FILES: []str = @[ 300 "src/history.c", 301]; 302 303LIBRARY_C_FILES: []str = @[ 304 "src/library.c", 305]; 306 307GEN_HEADER1: str = 308 "// Copyright (c) 2018-2025 Gavin D. Howard and contributors.\n" +~ 309 "// Licensed under the 2-clause BSD license.\n" +~ 310 "// *** AUTOMATICALLY GENERATED FROM "; 311GEN_HEADER2: str = ". DO NOT MODIFY. ***\n\n"; 312 313GEN_LABEL1: str = "const char *"; 314GEN_LABEL2: str = " = \""; 315GEN_LABEL3: str = "\";\n\n"; 316GEN_NAME1: str = "const char "; 317GEN_NAME2: str = "[] = {\n"; 318 319GEN_LABEL_EXTERN1: str = "extern const char *"; 320GEN_LABEL_EXTERN2: str = ";\n\n"; 321GEN_NAME_EXTERN1: str = "extern const char "; 322GEN_NAME_EXTERN2: str = "[];\n\n"; 323 324GEN_IFDEF1: str = "#if "; 325GEN_IFDEF2: str = "\n"; 326GEN_ENDIF1: str = "#endif // "; 327GEN_ENDIF2: str = "\n"; 328 329GEN_EX_START: str = "{{ A H N HN }}"; 330GEN_EX_END: str = "{{ end }}"; 331 332/// This is the max width to print characters to strgen files. This is to ensure 333/// that lines don't go much over 80 characters. 334MAX_WIDTH: usize = usize(72); 335 336/** 337 * A function to generate a C file that contains a C character array with the 338 * contents of a text file. For more detail, see the `gen/strgen.c` program; 339 * this function is exactly equivalent to that or should be. 340 * @param input The input file name. 341 * @param output The output file name. 342 * @param exclude True if extra math stuff should be excluded, false if 343 * they should be included. 344 * @param name The name of the array. 345 * @param label If not equal to "", this is the label for the array, 346 * which is essentially the "file name" in `bc` and `dc`. 347 * @param define If not equal to "", this is the preprocessor define 348 * expression that should be used to guard the array with a 349 * `#if`/`#endif` combo. 350 * @param remove_tabs True if tabs should be ignored, false if they should be 351 * included. 352 */ 353fn strgen( 354 input: str, 355 output: str, 356 exclude: bool, 357 name: str, 358 label: str, 359 def: str, 360 remove_tabs: bool, 361) -> void 362{ 363 in: str = io.read_file(input); 364 365 io.open(output, "w"): f 366 { 367 f.print(GEN_HEADER1 +~ input +~ GEN_HEADER2); 368 369 if label != "" 370 { 371 f.print(GEN_LABEL_EXTERN1 +~ label +~ GEN_LABEL_EXTERN2); 372 } 373 374 f.print(GEN_NAME_EXTERN1 +~ name +~ GEN_NAME_EXTERN2); 375 376 if def != "" 377 { 378 f.print(GEN_IFDEF1 +~ def +~ GEN_IFDEF2); 379 } 380 381 if label != "" 382 { 383 f.print(GEN_LABEL1 +~ label +~ GEN_LABEL2 +~ name +~ GEN_LABEL3); 384 } 385 386 f.print(GEN_NAME1 +~ name +~ GEN_NAME2); 387 388 i: !usize = usize(0); 389 count: !usize = usize(0); 390 slashes: !usize = usize(0); 391 392 // This is where the end of the license comment is found. 393 while slashes < 2 && in[i] > 0 394 { 395 if slashes == 1 && in[i] == '*' && in[i + 1] == '/' && 396 (in[i + 2] == '\n' || in[i + 2] == '\r') 397 { 398 slashes! = slashes + usize(1); 399 i! = i + usize(2); 400 } 401 else if slashes == 0 && in[i] == '/' && in[i + 1] == '*' 402 { 403 slashes! = slashes + usize(1); 404 i! = i + usize(1); 405 } 406 407 i! = i + usize(1); 408 } 409 410 // The file is invalid if the end of the license comment could not be 411 // found. 412 if i == in.len 413 { 414 error("Could not find end of license comment"); 415 } 416 417 i! = i + usize(1); 418 419 // Do not put extra newlines at the beginning of the char array. 420 while in[i] == '\n' || in[i] == '\r' 421 { 422 i! = i + usize(1); 423 } 424 425 // This loop is what generates the actual char array. It counts how many 426 // chars it has printed per line in order to insert newlines at 427 // appropriate places. It also skips tabs if they should be removed. 428 while i < in.len 429 { 430 if in[i] == '\r' 431 { 432 i! = i + usize(1); 433 continue; 434 } 435 436 // If we should output the character, i.e., it is not a tab or we 437 // can remove tabs... 438 if !remove_tabs || in[i] != '\t' 439 { 440 // Check for excluding something for extra math. 441 if in[i] == '{' 442 { 443 if i + GEN_EX_START.len <= in.len && 444 in.slice(i, i + GEN_EX_START.len) == GEN_EX_START 445 { 446 if exclude 447 { 448 // Get past the braces. 449 i! = i + usize(2); 450 451 // Find the end of the end. 452 while in[i] != '{' && 453 in.slice(i, i + GEN_EX_END.len) != GEN_EX_END 454 { 455 i! = i + usize(1); 456 } 457 458 i! = i + GEN_EX_END.len; 459 460 // Skip the last newline. 461 if in[i] == '\r' 462 { 463 i! = i + usize(1); 464 } 465 466 i! = i + usize(1); 467 468 continue; 469 } 470 } 471 else if !exclude && 472 in.slice(i, i + GEN_EX_END.len) == GEN_EX_END 473 { 474 i! = i + GEN_EX_END.len; 475 476 // Skip the last newline. 477 if in[i] == '\r' 478 { 479 i! = i + usize(1); 480 } 481 482 i! = i + usize(1); 483 484 continue; 485 } 486 } 487 488 // Print a tab if we are at the beginning of a line. 489 if count == 0 490 { 491 f.print("\t"); 492 } 493 494 val: str = str(in[i]) +~ ","; 495 496 // Print the character. 497 f.print(val); 498 499 // Adjust the count. 500 count! = count + val.len; 501 502 if count > MAX_WIDTH 503 { 504 count! = usize(0); 505 f.print("\n"); 506 } 507 } 508 509 i! = i + usize(1); 510 } 511 512 // Make sure the end looks nice. 513 if count == 0 514 { 515 f.print(" "); 516 } 517 518 // Insert the NUL byte at the end. 519 f.print("0\n};\n"); 520 521 if def != "" 522 { 523 f.print(GEN_ENDIF1 +~ def +~ GEN_ENDIF2); 524 } 525 } 526} 527 528/** 529 * Creates a target to generate an object file from the given C file and returns 530 * the target name of the new target. 531 * @param c_file The name of the C file target. 532 * @return The name of the object file target. 533 */ 534fn c2o(c_file: str) -> str 535{ 536 o_file: str = c_file +~ (if OS == "Windows" { ".obj"; } else { ".o"; }); 537 538 target o_file: c_file, HEADERS 539 { 540 $ $CC %(config_list["other_cflags"]) %(CFLAGS) $OBJOPT $OBJOUTOPT @(tgt) 541 @(file_dep); 542 } 543 544 return o_file; 545} 546 547/** 548 * Generates a target to turn a text file into a C file with the text file's 549 * contents as a char array, then generates a target to generate an object file 550 * from that C file, then returns the name of the object file target. 551 * @param txt_file The name of the text file. 552 * @param name The name of the char array in the C file. 553 * @param label The label for the array, if any. (See the @a strgen() 554 * function for more information.) 555 * @param def The preprocessor define(s) to guard the array, if any. 556 * (See the @a strgen() function for more information.) 557 * @param remove_tabs True if tabs should be ignored, false otherwise. (See the 558 * @a strgen() function for more information.) 559 * @return The name of the object file target. 560 */ 561fn txt2o( 562 txt_file: str, 563 name: str, 564 label: str, 565 def: str, 566 remove_tabs: bool, 567) -> str 568{ 569 c_file: str = txt_file +~ ".c"; 570 571 c_config: Gaml = @(gaml){ 572 strgen_name: $name 573 strgen_label: $label 574 strgen_define: $def 575 strgen_remove_tabs: $remove_tabs 576 }; 577 578 push c_config: config_stack 579 { 580 target c_file: txt_file 581 { 582 strgen(file_dep, tgt, EXTRA_MATH_ENABLED == "0", 583 str(config["strgen_name"]), str(config["strgen_label"]), 584 str(config["strgen_define"]), 585 bool(config["strgen_remove_tabs"])); 586 } 587 } 588 589 return c2o(c_file); 590} 591 592/** 593 * Generates a target for an executable and returns its name. 594 * @param name The name of the executable. 595 * @param o_files The object files for the executable. 596 * @return The name of the generated target. 597 */ 598fn exe(name: str, o_files: []str) -> void 599{ 600 target name: o_files 601 { 602 $ $CC %(config_list["other_cflags"]) %(config_list["strip_flag"]) 603 %(CFLAGS) %(LDFLAGS) $EXEOUTOPT @(tgt) %(file_deps); 604 } 605} 606 607/** 608 * Generates a target for a link. 609 * @param name The name of the link. 610 * @param exec The name of the executable target. 611 */ 612fn ln(name: str, exec: str) -> void 613{ 614 if OS == "Windows" 615 { 616 target name: exec 617 { 618 $ copy /v /y /b @(file_dep) @(tgt); 619 } 620 } 621 else 622 { 623 target name: exec 624 { 625 $ ln -fs @("./" +~ path.basename(file_dep)) @(tgt); 626 } 627 } 628} 629 630/** 631 * Generates a target for a library. 632 * @param name The name of the library. 633 * @param exec The name of the executable target. 634 */ 635fn lib(name: str, o_files: []str) -> void 636{ 637 if OS == "WINDOWS" 638 { 639 exe(name, o_files); 640 } 641 else 642 { 643 target name: o_files 644 { 645 $ ar -r -cu @(tgt) %(file_deps); 646 } 647 } 648} 649 650fn check_err_test( 651 name: str, 652 res: CmdResult, 653) -> void 654{ 655 if res.exitcode > 127 656 { 657 error("Test \"" +~ name +~ "\" crashed"); 658 } 659 660 if res.exitcode == 0 661 { 662 error("Test \"" +~ name +~ "\" returned no error"); 663 } 664 665 if res.exitcode == 100 666 { 667 error("Test \"" +~ name +~ "\" had memory errors on non-fatal error\n"); 668 } 669 670 if res.stderr.len <= 1 671 { 672 error("Test \"" +~ name +~ "\" produced no error message"); 673 } 674} 675 676fn check_test_retcode( 677 name: str, 678 exitcode: uint, 679) -> void 680{ 681 if exitcode != 0 682 { 683 error("Test \"" +~ name +~ "\" failed with exitcode: " +~ 684 str(exitcode) +~ "\n"); 685 } 686} 687 688fn check_test( 689 name: str, 690 res: CmdResult, 691 exp_path: str, 692) -> void 693{ 694 check_test_retcode(name, res.exitcode); 695 696 exp := io.read_file_bytes(exp_path); 697 698 if exp != res.stdout_full 699 { 700 error("Test \"" +~ name +~ "\" failed\n" +~ str(res.stderr)); 701 } 702} 703 704fn register_standard_tests( 705 bin: str, 706 testdir: str, 707 src_testdir: str, 708 extra: bool, 709) -> void 710{ 711 all_file: str = path.join(src_testdir, "all.txt"); 712 tests: []str = io.read_file(all_file).split("\n"); 713 714 extra_path := path.join(src_dir, "tests/extra_required.txt"); 715 extra_required: []str = io.read_file(extra_path).split("\n"); 716 717 for t: tests 718 { 719 if t == "" 720 { 721 continue; 722 } 723 724 // Skip extra math tests if it is not enabled. 725 if !extra && extra_required contains t 726 { 727 continue; 728 } 729 730 test sym(path.join(testdir, t)): bin 731 { 732 halt: str = str(config["halt"]); 733 734 name: str = path.basename(tgt_name); 735 testdir: str = path.dirname(tgt_name); 736 calc: str = path.basename(testdir); 737 738 test_file: str = tgt_name +~ ".txt"; 739 test_result_file: str = tgt_name +~ "_results.txt"; 740 741 src_test_file: str = path.join(src_dir, test_file); 742 src_test_result_file: str = path.join(src_dir, test_result_file); 743 744 actual_test_file: str = 745 if !path.isfile(src_test_file) 746 { 747 // If we shouldn't generate tests, skip. 748 if !bool(config["generated_tests"]) 749 { 750 io.eprint("Skipping test " +~ tgt_name +~ "\n"); 751 return; 752 } 753 754 script_name: str = name +~ "." +~ calc; 755 756 scriptdir: str = path.join(testdir, "scripts"); 757 src_scriptdir: str = path.join(src_dir, scriptdir); 758 src_script_name: str = path.join(src_scriptdir, script_name); 759 760 $ @(default_exe_name(calc)) $src_script_name > $test_file; 761 762 test_file; 763 } 764 else 765 { 766 src_test_file; 767 }; 768 769 exp_result_file: str = 770 if !path.isfile(src_test_result_file) 771 { 772 tmpfile: str = path.tmp(calc +~ "_test_result"); 773 774 $ @(default_exe_name(calc)) %(config_list["gen_options"]) 775 $actual_test_file << $halt > $tmpfile; 776 777 tmpfile; 778 } 779 else 780 { 781 src_test_result_file; 782 }; 783 784 res := $ %(config_list["args"]) %(config_list["options"]) 785 $actual_test_file << $halt; 786 787 check_test(tgt_name, res, exp_result_file); 788 } 789 } 790} 791 792fn register_script_tests( 793 bin: str, 794 testdir: str, 795 src_testdir: str, 796 extra: bool, 797) -> void 798{ 799 scriptdir: str = path.join(testdir, "scripts"); 800 src_scriptdir: str = path.join(src_testdir, "scripts"); 801 all_file: str = path.join(src_scriptdir, "all.txt"); 802 tests: []str = io.read_file(all_file).split("\n"); 803 804 for t: tests 805 { 806 if t == "" 807 { 808 continue; 809 } 810 811 // Skip extra math tests if it is not enabled. 812 if !extra && (t == "rand.bc" || t == "root.bc" || t == "i2rand.bc") 813 { 814 continue; 815 } 816 817 test sym(path.join(scriptdir, t)): bin 818 { 819 halt: str = str(config["halt"]); 820 821 name: str = path.basename(tgt_name); 822 testdir: str = path.dirname(tgt_name); 823 testdir2: str = path.dirname(testdir); 824 calc: str = path.basename(testdir2); 825 826 test_file: str = tgt_name; 827 test_file_dir: str = path.dirname(tgt_name); 828 test_file_name: str = path.basename(tgt_name, "." +~ calc); 829 test_result_file: str = path.join(test_file_dir, 830 test_file_name +~ ".txt"); 831 832 src_test_file: str = path.join(src_dir, test_file); 833 src_test_result_file: str = path.join(src_dir, test_result_file); 834 835 exp_result_file: str = 836 if !path.isfile(src_test_result_file) 837 { 838 tmpfile: str = path.tmp(calc +~ "_script_test_result"); 839 840 // This particular test needs to be generated straight. Also, on 841 // Windows, we don't have `sed`, and the `bc`/`dc` there is 842 // probably this one anyway. 843 if name == "stream.dc" || host.os == "Windows" 844 { 845 $ @(default_exe_name(calc)) $src_test_file << $halt 846 > $tmpfile; 847 } 848 else 849 { 850 root_testdir: str = path.join(src_dir, "tests"); 851 852 // This sed and the script are to remove an incompatibility 853 // with GNU bc, where GNU bc is wrong. See the development 854 // manual (manuals/development.md#script-tests) for more 855 // information. 856 $ @(default_exe_name(calc)) $src_test_file << $halt | 857 sed -n -f @(path.join(root_testdir, "script.sed")) 858 > $tmpfile; 859 } 860 861 tmpfile; 862 } 863 else 864 { 865 src_test_result_file; 866 }; 867 868 if calc == "bc" 869 { 870 res1 := $ %(config_list["args"]) -g 871 %(config_list["script_options"]) $src_test_file 872 << $halt; 873 874 check_test(tgt_name, res1, exp_result_file); 875 } 876 877 // These tests do not need to run without global stacks. 878 if name == "globals.bc" || name == "references.bc" || 879 name == "rand.bc" 880 { 881 return; 882 } 883 884 res2 := $ %(config_list["args"]) %(config_list["script_options"]) 885 $src_test_file << $halt; 886 887 check_test(tgt_name, res2, exp_result_file); 888 } 889 } 890} 891 892fn register_stdin_test( 893 bin: str, 894 testdir: str, 895 name: str 896) -> void 897{ 898 test sym(path.join(testdir, name)): bin 899 { 900 name: str = path.basename(tgt_name); 901 testdir: str = path.dirname(tgt_name); 902 calc: str = path.basename(testdir); 903 904 halt: str = if name == "bc" { "halt"; } else { "q"; }; 905 906 test_file: str = tgt_name +~ ".txt"; 907 test_result_file: str = tgt_name +~ "_results.txt"; 908 909 src_test_file: str = path.join(src_dir, test_file); 910 src_test_result_file: str = path.join(src_dir, test_result_file); 911 912 res := $ %(config_list["args"]) %(config_list["options"]) 913 < $src_test_file; 914 915 check_test(tgt_name, res, src_test_result_file); 916 } 917} 918 919fn register_stdin_tests( 920 bin: str, 921 testdir: str, 922 src_testdir: str, 923) -> void 924{ 925 calc: str = path.basename(testdir); 926 927 if calc == "bc" 928 { 929 for t: @[ "stdin", "stdin1", "stdin2" ] 930 { 931 register_stdin_test(bin, testdir, t); 932 } 933 } 934 else 935 { 936 // dc only needs one. 937 register_stdin_test(bin, testdir, "stdin"); 938 } 939} 940 941fn register_read_tests( 942 bin: str, 943 testdir: str, 944 src_testdir: str, 945) -> void 946{ 947 calc: str = path.basename(testdir); 948 949 read_call: str = if calc == "bc" { "read()"; } else { "?"; }; 950 read_expr: str = 951 if calc == "bc" 952 { 953 read_call +~ "\n5+5;"; 954 } 955 else 956 { 957 read_call; 958 }; 959 read_multiple: str = 960 if calc == "bc" 961 { 962 "3\n2\n1\n"; 963 } 964 else 965 { 966 "3pR\n2pR\n1pR\n"; 967 }; 968 969 read_test_config: Gaml = @(gaml){ 970 read_call: $read_call 971 read_expr: $read_expr 972 read_multiple: $read_multiple 973 }; 974 975 push read_test_config: config_stack 976 { 977 // First test is the regular read test. 978 test sym(path.join(testdir, "read")): bin 979 { 980 testdir: str = path.dirname(tgt_name); 981 src_testdir: str = path.join(src_dir, testdir); 982 983 test_file: str = tgt_name +~ ".txt"; 984 src_test_file: str = path.join(src_dir, test_file); 985 986 read_call: str = str(config["read_call"]); 987 988 lines: []str = io.read_file(src_test_file).split("\n"); 989 990 for l: lines 991 { 992 if l == "" 993 { 994 continue; 995 } 996 997 res := $ %(config_list["args"]) %(config_list["options"]) 998 << @(read_call +~ "\n" +~ l +~ "\n"); 999 1000 check_test(tgt_name, res, 1001 path.join(src_testdir, "read_results.txt")); 1002 } 1003 } 1004 1005 // Next test is reading multiple times. 1006 test sym(path.join(testdir, "read_multiple")): bin 1007 { 1008 testdir: str = path.dirname(tgt_name); 1009 1010 test_file: str = tgt_name +~ ".txt"; 1011 1012 path.mkdirp(path.dirname(test_file)); 1013 1014 read_call: str = str(config["read_call"]); 1015 1016 exp_path: str = path.tmp("read_multiple_results"); 1017 1018 io.open(exp_path, "w"): f 1019 { 1020 f.print("3\n2\n1\n"); 1021 } 1022 1023 res := $ %(config_list["args"]) %(config_list["options"]) 1024 -e $read_call -e $read_call -e $read_call 1025 << @(str(config["read_multiple"])); 1026 1027 check_test(tgt_name, res, exp_path); 1028 } 1029 1030 // Next test is the read errors test. 1031 test sym(path.join(testdir, "read_errors")): bin 1032 { 1033 testdir: str = path.dirname(tgt_name); 1034 src_testdir: str = path.join(src_dir, testdir); 1035 1036 test_file: str = tgt_name +~ ".txt"; 1037 src_test_file: str = path.join(src_dir, test_file); 1038 1039 path.mkdirp(path.dirname(test_file)); 1040 1041 read_call: str = str(config["read_call"]); 1042 1043 lines: []str = io.read_file(src_test_file).split("\n"); 1044 1045 for l: lines 1046 { 1047 if l == "" 1048 { 1049 continue; 1050 } 1051 1052 res := $ %(config_list["args"]) %(config_list["options"]) 1053 << @(read_call +~ "\n" +~ l +~ "\n"); 1054 1055 check_err_test(tgt_name, res); 1056 } 1057 } 1058 1059 // Next test is the empty read test. 1060 test sym(path.join(testdir, "read_empty")): bin 1061 { 1062 read_call: str = str(config["read_call"]); 1063 1064 res := $ %(config_list["args"]) %(config_list["options"]) 1065 << @(read_call +~ "\n"); 1066 1067 check_err_test(tgt_name, res); 1068 } 1069 1070 // Next test is the read EOF test. 1071 test sym(path.join(testdir, "read_EOF")): bin 1072 { 1073 read_call: str = str(config["read_call"]); 1074 1075 res := $ %(config_list["args"]) %(config_list["options"]) 1076 << $read_call; 1077 1078 check_err_test(tgt_name, res); 1079 } 1080 } 1081} 1082 1083fn run_error_lines_test(name: str) -> void 1084{ 1085 file: str = path.join(src_dir, name); 1086 1087 lines: []str = io.read_file(file).split("\n"); 1088 1089 for l: lines 1090 { 1091 if l == "" 1092 { 1093 continue; 1094 } 1095 1096 res := $ %(config_list["args"]) %(config_list["options"]) 1097 %(config_list["error_options"]) << @(l +~ "\n"); 1098 1099 check_err_test(name, res); 1100 } 1101} 1102 1103fn register_error_tests( 1104 bin: str, 1105 testdir: str, 1106 src_testdir: str, 1107) -> void 1108{ 1109 calc: str = path.basename(testdir); 1110 1111 // First test is command-line expression error. 1112 test sym(path.join(testdir, "command-line_expr_error")): bin 1113 { 1114 halt: str = str(config["halt"]); 1115 1116 res := $ %(config_list["args"]) %(config_list["options"]) -e "1+1" -f- 1117 -e "2+2" << $halt; 1118 1119 check_err_test(tgt_name, res); 1120 } 1121 1122 // First test is command-line file expression error. 1123 test sym(path.join(testdir, "command-line_file_expr_error")): bin 1124 { 1125 testdir: str = path.dirname(tgt_name); 1126 halt: str = str(config["halt"]); 1127 1128 res := $ %(config_list["args"]) %(config_list["options"]) -e "1+1" -f- 1129 -f @(path.join(testdir, "decimal.txt")) << $halt; 1130 1131 check_err_test(tgt_name, res); 1132 } 1133 1134 if calc == "bc" 1135 { 1136 test sym(path.join(testdir, "posix_warning")): bin 1137 { 1138 res := $ %(config_list["args"]) %(config_list["options"]) -w 1139 << @("line"); 1140 1141 if res.exitcode != 0 1142 { 1143 error("Test \"" +~ tgt_name +~ "\" returned an error (" +~ 1144 str(res.exitcode) +~ ")"); 1145 } 1146 1147 output: str = str(res.stderr); 1148 1149 if output == "" || output == "\n" 1150 { 1151 error("Test \"" +~ tgt_name +~ "\" did not print a warning"); 1152 } 1153 } 1154 1155 test sym(path.join(testdir, "posix_errors.txt")): bin 1156 { 1157 run_error_lines_test(tgt_name); 1158 } 1159 } 1160 1161 test sym(path.join(testdir, "errors.txt")): bin 1162 { 1163 run_error_lines_test(tgt_name); 1164 } 1165 1166 errors_dir: str = path.join(testdir, "errors"); 1167 1168 for f: find_src_ext(errors_dir, "txt") 1169 { 1170 // Skip the problematic test, if requested. 1171 if calc == "bc" && f contains "33.txt" && 1172 !bool(config["problematic_tests"]) 1173 { 1174 continue; 1175 } 1176 1177 test sym(f): bin 1178 { 1179 errors_dir: str = path.dirname(tgt_name); 1180 testdir: str = path.dirname(errors_dir); 1181 calc: str = path.basename(testdir); 1182 1183 halt: str = str(config["halt"]); 1184 1185 res1 := $ %(config_list["args"]) %(config_list["error_options"]) -c 1186 @(tgt_name) << $halt; 1187 1188 check_err_test(tgt_name, res1); 1189 1190 res2 := $ %(config_list["args"]) %(config_list["error_options"]) -C 1191 @(tgt_name) << $halt; 1192 1193 check_err_test(tgt_name, res2); 1194 1195 res3 := $ %(config_list["args"]) %(config_list["error_options"]) -c 1196 < @(path.join(src_dir, tgt_name)); 1197 1198 check_err_test(tgt_name, res3); 1199 1200 res4 := $ %(config_list["args"]) %(config_list["error_options"]) -C 1201 < @(path.join(src_dir, tgt_name)); 1202 1203 check_err_test(tgt_name, res4); 1204 } 1205 } 1206} 1207 1208fn check_kwredef_test( 1209 name: str, 1210 res: CmdResult, 1211) -> void 1212{ 1213 testdir: str = path.dirname(name); 1214 redefine_exp: str = path.join(testdir, "redefine_exp.txt"); 1215 1216 check_test(tgt_name, res, redefine_exp); 1217} 1218 1219OTHER_LINE_LEN_RESULTS_NAME: str = "line_length_test_results.txt"; 1220OTHER_LINE_LEN70_RESULTS_NAME: str = "line_length70_test_results.txt"; 1221OTHER_MATHLIB_SCALE_RESULTS_NAME: str = "mathlib_scale_results.txt"; 1222 1223fn register_other_tests( 1224 bin: str, 1225 testdir: str, 1226 src_testdir: str, 1227 extra: bool, 1228) -> void 1229{ 1230 calc: str = path.basename(testdir); 1231 1232 path.mkdirp(testdir); 1233 1234 // Halt test. 1235 test sym(path.join(testdir, "halt")): bin 1236 { 1237 halt: str = str(config["halt"]) +~ "\n"; 1238 1239 res := $ %(config_list["args"]) << $halt; 1240 1241 check_test_retcode(tgt_name, res.exitcode); 1242 } 1243 1244 if calc == "bc" 1245 { 1246 // bc has two halt or quit commands, so test the second as well. 1247 test sym(path.join(testdir, "quit")): bin 1248 { 1249 res := $ %(config_list["args"]) << @("quit\n"); 1250 1251 check_test_retcode(tgt_name, res.exitcode); 1252 } 1253 1254 // Also, make sure quit only quits after an expression. 1255 test sym(path.join(testdir, "quit_after_expr")): bin 1256 { 1257 res := $ %(config_list["args"]) -e "1+1" << @("quit\n"); 1258 1259 check_test_retcode(tgt_name, res.exitcode); 1260 1261 if str(res.stdout) != "2" 1262 { 1263 error("Test \"" +~ tgt_name +~ 1264 "\" did not have the right output"); 1265 } 1266 } 1267 1268 test sym(path.join(testdir, "env_args1")): bin 1269 { 1270 env.set env.str("BC_ENV_ARGS", " '-l' '' -q") 1271 { 1272 res := $ %(config_list["args"]) << @("s(.02893)\n"); 1273 1274 check_test_retcode(tgt_name, res.exitcode); 1275 } 1276 } 1277 1278 test sym(path.join(testdir, "env_args2")): bin 1279 { 1280 env.set env.str("BC_ENV_ARGS", " '-l' '' -q") 1281 { 1282 res := $ %(config_list["args"]) -e 4 << @("halt\n"); 1283 1284 check_test_retcode(tgt_name, res.exitcode); 1285 } 1286 } 1287 1288 redefine_exp: str = path.join(testdir, "redefine_exp.txt"); 1289 1290 io.open(redefine_exp, "w"): f 1291 { 1292 f.print("5\n0\n"); 1293 } 1294 1295 test sym(path.join(testdir, "keyword_redefinition1")): bin 1296 { 1297 res := $ %(config_list["args"]) --redefine=print -e 1298 "define print(x) { x }" -e "print(5)" << @("halt\n"); 1299 1300 check_kwredef_test(tgt_name, res); 1301 } 1302 1303 test sym(path.join(testdir, "keyword_redefinition2")): bin 1304 { 1305 res := $ %(config_list["args"]) -r abs -r else -e 1306 "abs = 5; else = 0" -e "abs;else" << @("halt\n"); 1307 1308 check_kwredef_test(tgt_name, res); 1309 } 1310 1311 if extra 1312 { 1313 test sym(path.join(testdir, "keyword_redefinition_lib2")): bin 1314 { 1315 res := $ %(config_list["args"]) -lr abs -e "perm(5, 1)" -e 0 1316 << @("halt\n"); 1317 1318 check_kwredef_test(tgt_name, res); 1319 } 1320 1321 test sym(path.join(testdir, "leading_zero_script")): bin 1322 { 1323 testdir: str = path.dirname(tgt_name); 1324 src_testdir: str = path.join(src_dir, testdir); 1325 1326 res := $ %(config_list["args"]) -lz 1327 @(path.join(src_testdir, "leadingzero.txt")) 1328 << @(str(config["halt"])); 1329 1330 check_test(tgt_name, res, 1331 path.join(src_testdir, "leadingzero_results.txt")); 1332 } 1333 } 1334 1335 test sym(path.join(testdir, "keyword_redefinition3")): bin 1336 { 1337 res := $ %(config_list["args"]) -r abs -r else -e 1338 "abs = 5; else = 0" -e "abs;else" << @("halt\n"); 1339 1340 check_kwredef_test(tgt_name, res); 1341 } 1342 1343 test sym(path.join(testdir, "keyword_redefinition_error")): bin 1344 { 1345 res := $ %(config_list["args"]) -r break -e "define break(x) { x }"; 1346 1347 check_err_test(tgt_name, res); 1348 } 1349 1350 test sym(path.join(testdir, 1351 "keyword_redefinition_without_redefine")): bin 1352 { 1353 res := $ %(config_list["args"]) -e "define read(x) { x }"; 1354 1355 check_err_test(tgt_name, res); 1356 } 1357 1358 test sym(path.join(testdir, "multiline_comment_in_expr_file")): bin 1359 { 1360 testdir: str = path.dirname(tgt_name); 1361 src_testdir: str = path.join(src_dir, testdir); 1362 1363 // tests/bc/misc1.txt happens to have a multiline comment in it. 1364 src_test_file: str = path.join(src_testdir, "misc1.txt"); 1365 src_test_results_file: str = path.join(src_testdir, 1366 "misc1_results.txt"); 1367 1368 res := $ %(config_list["args"]) -f $src_test_file << @("halt\n"); 1369 1370 check_test(tgt_name, res, src_test_results_file); 1371 } 1372 1373 test sym(path.join(testdir, 1374 "multiline_comment_error_in_expr_file")): bin 1375 { 1376 testdir: str = path.dirname(tgt_name); 1377 src_testdir: str = path.join(src_dir, testdir); 1378 1379 src_test_file: str = path.join(src_testdir, "errors/05.txt"); 1380 1381 res := $ %(config_list["args"]) -f $src_test_file << @("halt\n"); 1382 1383 check_err_test(tgt_name, res); 1384 } 1385 1386 test sym(path.join(testdir, "multiline_string_in_expr_file")): bin 1387 { 1388 testdir: str = path.dirname(tgt_name); 1389 src_testdir: str = path.join(src_dir, testdir); 1390 1391 // tests/bc/strings.txt happens to have a multiline string in it. 1392 src_test_file: str = path.join(src_testdir, "strings.txt"); 1393 src_test_results_file: str = path.join(src_testdir, 1394 "strings_results.txt"); 1395 1396 res := $ %(config_list["args"]) -f $src_test_file << @("halt\n"); 1397 1398 check_test(tgt_name, res, src_test_results_file); 1399 } 1400 1401 tst := path.join(testdir, 1402 "multiline_string_with_backslash_error_in_expr_file"); 1403 1404 test sym(tst): bin 1405 { 1406 testdir: str = path.dirname(tgt_name); 1407 src_testdir: str = path.join(src_dir, testdir); 1408 1409 src_test_file: str = path.join(src_testdir, "errors/16.txt"); 1410 1411 res := $ %(config_list["args"]) -f $src_test_file << @("halt\n"); 1412 1413 check_err_test(tgt_name, res); 1414 } 1415 1416 tst2 := path.join(testdir, "multiline_string_error_in_expr_file"); 1417 1418 test sym(tst2): bin 1419 { 1420 testdir: str = path.dirname(tgt_name); 1421 src_testdir: str = path.join(src_dir, testdir); 1422 1423 src_test_file: str = path.join(src_testdir, "errors/04.txt"); 1424 1425 res := $ %(config_list["args"]) -f $src_test_file << @("halt\n"); 1426 1427 check_err_test(tgt_name, res); 1428 } 1429 1430 test sym(path.join(testdir, "interactive_halt")): bin 1431 { 1432 res := $ %(config_list["args"]) -i << @("halt\n"); 1433 1434 check_test_retcode(tgt_name, res.exitcode); 1435 } 1436 } 1437 else 1438 { 1439 test sym(path.join(testdir, "env_args1")): bin 1440 { 1441 env.set env.str("DC_ENV_ARGS", "'-x'"), env.str("DC_EXPR_EXIT", "1") 1442 { 1443 res := $ %(config_list["args"]) << @("4s stuff\n"); 1444 1445 check_test_retcode(tgt_name, res.exitcode); 1446 } 1447 } 1448 1449 test sym(path.join(testdir, "env_args2")): bin 1450 { 1451 env.set env.str("DC_ENV_ARGS", "'-x'"), env.str("DC_EXPR_EXIT", "1") 1452 { 1453 res := $ %(config_list["args"]) -e 4pR; 1454 1455 check_test_retcode(tgt_name, res.exitcode); 1456 } 1457 } 1458 1459 test sym(path.join(testdir, "extended_register_command1")): bin 1460 { 1461 testdir: str = path.dirname(tgt_name); 1462 results: str = tgt_name +~ ".txt"; 1463 1464 path.mkdirp(testdir); 1465 1466 io.open(results, "w"): f 1467 { 1468 f.print("0\n"); 1469 } 1470 1471 res := $ %(config_list["args"]) -e gxpR << @("q\n"); 1472 1473 check_test(tgt_name, res, results); 1474 } 1475 1476 test sym(path.join(testdir, "extended_register_command2")): bin 1477 { 1478 testdir: str = path.dirname(tgt_name); 1479 results: str = tgt_name +~ ".txt"; 1480 1481 path.mkdirp(testdir); 1482 1483 io.open(results, "w"): f 1484 { 1485 f.print("1\n"); 1486 } 1487 1488 res := $ %(config_list["args"]) -x -e gxpR << @("q\n"); 1489 1490 check_test(tgt_name, res, results); 1491 } 1492 } 1493 1494 path.mkdirp(testdir); 1495 1496 other_tests_results: []str = config_list["other_tests_results"]; 1497 1498 io.open(path.join(testdir, OTHER_LINE_LEN_RESULTS_NAME), "w"): f 1499 { 1500 f.print(other_tests_results[0] +~ "\n"); 1501 } 1502 1503 io.open(path.join(testdir, OTHER_LINE_LEN70_RESULTS_NAME), "w"): f 1504 { 1505 f.print(other_tests_results[1] +~ "\n"); 1506 } 1507 1508 test sym(path.join(testdir, "line_length1")): bin 1509 { 1510 env.set env.str(str(config["var"]), "80") 1511 { 1512 testdir: str = path.dirname(tgt_name); 1513 1514 other_tests: []str = config_list["other_tests"]; 1515 1516 res := $ %(config_list["args"]) << @(other_tests[3]); 1517 1518 check_test(tgt_name, res, 1519 path.join(testdir, OTHER_LINE_LEN_RESULTS_NAME)); 1520 } 1521 } 1522 1523 test sym(path.join(testdir, "line_length2")): bin 1524 { 1525 env.set env.str(str(config["var"]), "2147483647") 1526 { 1527 testdir: str = path.dirname(tgt_name); 1528 1529 other_tests: []str = config_list["other_tests"]; 1530 1531 res := $ %(config_list["args"]) << @(other_tests[3]); 1532 1533 check_test(tgt_name, res, 1534 path.join(testdir, OTHER_LINE_LEN70_RESULTS_NAME)); 1535 } 1536 } 1537 1538 test sym(path.join(testdir, "expr_and_file_args_test")): bin 1539 { 1540 testdir: str = path.dirname(tgt_name); 1541 src_testdir: str = path.join(src_dir, testdir); 1542 1543 input_file: str = path.join(src_testdir, "add.txt"); 1544 input: str = io.read_file(input_file); 1545 results_file: str = path.join(src_testdir, "add_results.txt"); 1546 results: str = io.read_file(results_file); 1547 1548 output_file: str = path.join(testdir, "expr_file_args.txt"); 1549 1550 io.open(output_file, "w"): f 1551 { 1552 f.print(results +~ results +~ results +~ results); 1553 } 1554 1555 res := $ %(config_list["args"]) -e $input -f $input_file 1556 --expression $input --file $input_file 1557 -e @(str(config["halt"])); 1558 1559 check_test(tgt_name, res, output_file); 1560 } 1561 1562 test sym(path.join(testdir, "files_test")): bin 1563 { 1564 env.set env.str(str(config["var"]), "2147483647") 1565 { 1566 testdir: str = path.dirname(tgt_name); 1567 src_testdir: str = path.join(src_dir, testdir); 1568 1569 input_file: str = path.join(src_testdir, "add.txt"); 1570 input: str = io.read_file(input_file); 1571 results_file: str = path.join(src_testdir, "add_results.txt"); 1572 results: str = io.read_file(results_file); 1573 1574 output_file: str = path.join(testdir, "files.txt"); 1575 1576 io.open(output_file, "w"): f 1577 { 1578 f.print(results +~ results +~ results +~ results); 1579 } 1580 1581 res := $ %(config_list["args"]) -- $input_file $input_file 1582 $input_file $input_file << @(str(config["halt"])); 1583 1584 check_test(tgt_name, res, output_file); 1585 } 1586 } 1587 1588 test sym(path.join(testdir, "line_length3")): bin 1589 { 1590 env.set env.str(str(config["var"]), "62") 1591 { 1592 testdir: str = path.dirname(tgt_name); 1593 1594 other_tests: []str = config_list["other_tests"]; 1595 1596 res := $ %(config_list["args"]) -L << @(other_tests[3]); 1597 1598 check_test(tgt_name, res, 1599 path.join(testdir, OTHER_LINE_LEN_RESULTS_NAME)); 1600 } 1601 } 1602 1603 test sym(path.join(testdir, "line_length_func")): bin 1604 { 1605 env.set env.str(str(config["var"]), "62") 1606 { 1607 testdir: str = path.dirname(tgt_name); 1608 results: str = tgt_name +~ ".txt"; 1609 1610 path.mkdirp(testdir); 1611 1612 io.open(results, "w"): f 1613 { 1614 f.print("0\n"); 1615 } 1616 1617 other_tests: []str = config_list["other_tests"]; 1618 1619 res := $ %(config_list["args"]) -L << @(other_tests[2]); 1620 1621 check_test(tgt_name, res, results); 1622 } 1623 } 1624 1625 test sym(path.join(testdir, "arg")): bin 1626 { 1627 halt: str = str(config["halt"]); 1628 1629 res1 := $ %(config_list["args"]) -h << $halt; 1630 check_test_retcode(tgt_name, res1.exitcode); 1631 1632 res2 := $ %(config_list["args"]) -P << $halt; 1633 check_test_retcode(tgt_name, res2.exitcode); 1634 1635 res3 := $ %(config_list["args"]) -R << $halt; 1636 check_test_retcode(tgt_name, res3.exitcode); 1637 1638 res4 := $ %(config_list["args"]) -v << $halt; 1639 check_test_retcode(tgt_name, res4.exitcode); 1640 1641 res5 := $ %(config_list["args"]) -V << $halt; 1642 check_test_retcode(tgt_name, res5.exitcode); 1643 } 1644 1645 test sym(path.join(testdir, "leading_zero_arg")): bin 1646 { 1647 testdir: str = path.dirname(tgt_name); 1648 calc: str = path.basename(testdir); 1649 1650 expected_file: str = tgt_name +~ ".txt"; 1651 1652 expected: str = "0.1\n-0.1\n1.1\n-1.1\n0.1\n-0.1\n"; 1653 1654 io.open(expected_file, "w"): f 1655 { 1656 f.print(expected); 1657 } 1658 1659 data: str = 1660 if calc == "bc" 1661 { 1662 "0.1\n-0.1\n1.1\n-1.1\n.1\n-.1\n"; 1663 } 1664 else 1665 { 1666 "0.1pR\n_0.1pR\n1.1pR\n_1.1pR\n.1pR\n_.1pR\n"; 1667 }; 1668 1669 res := $ %(config_list["args"]) -z << $data; 1670 1671 check_test(tgt_name, res, expected_file); 1672 } 1673 1674 test sym(path.join(testdir, "invalid_file_arg")): bin 1675 { 1676 res := $ %(config_list["args"]) -f 1677 "astoheusanotehynstahonsetihaotsnuhynstahoaoetusha.txt"; 1678 1679 check_err_test(tgt_name, res); 1680 } 1681 1682 test sym(path.join(testdir, "invalid_option_arg")): bin 1683 { 1684 other_tests: []str = config_list["other_tests"]; 1685 1686 res := $ %(config_list["args"]) @("-" +~ other_tests[0]) 1687 -e @(str(config["halt"])); 1688 1689 check_err_test(tgt_name, res); 1690 } 1691 1692 test sym(path.join(testdir, "invalid_long_option_arg")): bin 1693 { 1694 other_tests: []str = config_list["other_tests"]; 1695 1696 res := $ %(config_list["args"]) @("--" +~ other_tests[1]) 1697 -e @(str(config["halt"])); 1698 1699 check_err_test(tgt_name, res); 1700 } 1701 1702 test sym(path.join(testdir, "unrecognized_option_arg")): bin 1703 { 1704 res := $ %(config_list["args"]) -u -e @(str(config["halt"])); 1705 1706 check_err_test(tgt_name, res); 1707 } 1708 1709 test sym(path.join(testdir, "unrecognized_long_option_arg")): bin 1710 { 1711 res := $ %(config_list["args"]) --uniform -e @(str(config["halt"])); 1712 1713 check_err_test(tgt_name, res); 1714 } 1715 1716 test sym(path.join(testdir, "no_required_arg_for_option")): bin 1717 { 1718 res := $ %(config_list["args"]) -f; 1719 1720 check_err_test(tgt_name, res); 1721 } 1722 1723 test sym(path.join(testdir, "no_required_arg_for_long_option")): bin 1724 { 1725 res := $ %(config_list["args"]) --file; 1726 1727 check_err_test(tgt_name, res); 1728 } 1729 1730 test sym(path.join(testdir, "given_arg_for_long_option_with_no_arg")): bin 1731 { 1732 res := $ %(config_list["args"]) --version=5; 1733 1734 check_err_test(tgt_name, res); 1735 } 1736 1737 test sym(path.join(testdir, "colon_option")): bin 1738 { 1739 res := $ %(config_list["args"]) -:; 1740 1741 check_err_test(tgt_name, res); 1742 } 1743 1744 test sym(path.join(testdir, "colon_long_option")): bin 1745 { 1746 res := $ %(config_list["args"]) --:; 1747 1748 check_err_test(tgt_name, res); 1749 } 1750 1751 test sym(path.join(testdir, "builtin_variable_arg_test")): bin 1752 { 1753 testdir: str = path.dirname(tgt_name); 1754 calc: str = path.basename(testdir); 1755 1756 extra: bool = bool(config["extra_math"]); 1757 1758 output: str = 1759 if extra 1760 { 1761 "14\n15\n16\n17.25\n"; 1762 } 1763 else 1764 { 1765 "14\n15\n16\n"; 1766 }; 1767 1768 output_file: str = tgt_name +~ ".txt"; 1769 1770 io.open(output_file, "w"): f 1771 { 1772 f.print(output); 1773 } 1774 1775 data: str = 1776 if extra 1777 { 1778 if calc == "bc" 1779 { 1780 "s=scale;i=ibase;o=obase;t=seed@2;ibase=A;obase=A;s;i;o;t;"; 1781 } 1782 else 1783 { 1784 "J2@OIKAiAopRpRpRpR"; 1785 } 1786 } 1787 else 1788 { 1789 if calc == "bc" 1790 { 1791 "s=scale;i=ibase;o=obase;ibase=A;obase=A;s;i;o;"; 1792 } 1793 else 1794 { 1795 "OIKAiAopRpRpR"; 1796 } 1797 }; 1798 1799 args: []str = 1800 if extra 1801 { 1802 @[ "-S14", "-I15", "-O16", "-E17.25" ]; 1803 } 1804 else 1805 { 1806 @[ "-S14", "-I15", "-O16" ]; 1807 }; 1808 1809 res1 := $ %(config_list["args"]) %(args) << $data; 1810 check_test(tgt_name, res1, output_file); 1811 1812 long_args: []str = 1813 if extra 1814 { 1815 @[ "--scale=14", "--ibase=15", "--obase=16", "--seed=17.25" ]; 1816 } 1817 else 1818 { 1819 @[ "--scale=14", "--ibase=15", "--obase=16" ]; 1820 }; 1821 1822 res2 := $ %(config_list["args"]) %(long_args) << $data; 1823 check_test(tgt_name, res2, output_file); 1824 } 1825 1826 if calc == "bc" 1827 { 1828 io.open(path.join(testdir, OTHER_MATHLIB_SCALE_RESULTS_NAME), "w"): f 1829 { 1830 f.print("100\n"); 1831 } 1832 1833 test sym(path.join(testdir, "builtin_var_arg_with_lib")): bin 1834 { 1835 testdir: str = path.dirname(tgt_name); 1836 results_file: str = path.join(testdir, 1837 OTHER_MATHLIB_SCALE_RESULTS_NAME); 1838 1839 res := $ %(config_list["args"]) -S100 -l << @("scale\n"); 1840 1841 check_test(tgt_name, res, results_file); 1842 } 1843 1844 test sym(path.join(testdir, "builtin_variable_long_arg_with_lib")): bin 1845 { 1846 testdir: str = path.dirname(tgt_name); 1847 results_file: str = path.join(testdir, 1848 OTHER_MATHLIB_SCALE_RESULTS_NAME); 1849 1850 res := $ %(config_list["args"]) --scale=100 --mathlib << 1851 @("scale\n"); 1852 1853 check_test(tgt_name, res, results_file); 1854 } 1855 1856 test sym(path.join(testdir, "builtin_var_arg_with_lib_env_arg")): bin 1857 { 1858 env.set env.str("BC_ENV_ARGS", "-l") 1859 { 1860 testdir: str = path.dirname(tgt_name); 1861 results_file: str = path.join(testdir, 1862 OTHER_MATHLIB_SCALE_RESULTS_NAME); 1863 1864 res := $ %(config_list["args"]) -S100 << @("scale\n"); 1865 1866 check_test(tgt_name, res, results_file); 1867 } 1868 } 1869 1870 test sym(path.join(testdir, 1871 "builtin_var_long_arg_with_lib_env_arg")): bin 1872 { 1873 env.set env.str("BC_ENV_ARGS", "-l") 1874 { 1875 testdir: str = path.dirname(tgt_name); 1876 results_file: str = path.join(testdir, 1877 OTHER_MATHLIB_SCALE_RESULTS_NAME); 1878 1879 res := $ %(config_list["args"]) --scale=100 << @("scale\n"); 1880 1881 check_test(tgt_name, res, results_file); 1882 } 1883 } 1884 1885 test sym(path.join(testdir, "builtin_var_env_arg_with_lib_arg")): bin 1886 { 1887 env.set env.str("BC_ENV_ARGS", "-S100") 1888 { 1889 testdir: str = path.dirname(tgt_name); 1890 results_file: str = path.join(testdir, 1891 OTHER_MATHLIB_SCALE_RESULTS_NAME); 1892 1893 res := $ %(config_list["args"]) -l << @("scale\n"); 1894 1895 check_test(tgt_name, res, results_file); 1896 } 1897 } 1898 1899 test sym(path.join(testdir, 1900 "builtin_var_long_env_arg_with_lib_arg")): bin 1901 { 1902 env.set env.str("BC_ENV_ARGS", "--scale=100") 1903 { 1904 testdir: str = path.dirname(tgt_name); 1905 results_file: str = path.join(testdir, 1906 OTHER_MATHLIB_SCALE_RESULTS_NAME); 1907 1908 res := $ %(config_list["args"]) -l << @("scale\n"); 1909 1910 check_test(tgt_name, res, results_file); 1911 } 1912 } 1913 1914 test sym(path.join(testdir, "limits")): bin 1915 { 1916 res := $ %(config_list["args"]) << @("limits\n"); 1917 1918 check_test_retcode(tgt_name, res.exitcode); 1919 1920 if str(res.stdout) == "" || str(res.stdout) == "\n" 1921 { 1922 error("Test \"" +~ tgt_name +~ "\" did not produce output"); 1923 } 1924 } 1925 } 1926 1927 test sym(path.join(testdir, "bad_arg_for_builtin_var_option")): bin 1928 { 1929 testdir: str = path.dirname(tgt_name); 1930 calc: str = path.basename(testdir); 1931 1932 scale: str = if calc == "bc" { "scale\n"; } else { "K\n"; }; 1933 1934 res1 := $ %(config_list["args"]) --scale=18923c.rlg << $scale; 1935 1936 check_err_test(tgt_name, res1); 1937 1938 if bool(config["extra_math"]) 1939 { 1940 seed: str = if calc == "bc" { "seed\n"; } else { "J\n"; }; 1941 1942 res2 := $ %(config_list["args"]) --seed=18923c.rlg << $seed; 1943 1944 check_err_test(tgt_name, res2); 1945 } 1946 } 1947 1948 test sym(path.join(testdir, "directory_as_file")): bin 1949 { 1950 testdir: str = path.dirname(tgt_name); 1951 1952 res := $ %(config_list["args"]) $testdir; 1953 1954 check_err_test(tgt_name, res); 1955 } 1956 1957 test sym(path.join(testdir, "binary_file")): bin 1958 { 1959 res := $ %(config_list["args"]) @(file_dep); 1960 1961 check_err_test(tgt_name, res); 1962 } 1963 1964 test sym(path.join(testdir, "binary_stdin")): bin 1965 { 1966 res := $ %(config_list["args"]) < @(file_dep); 1967 1968 check_err_test(tgt_name, res); 1969 } 1970} 1971 1972fn register_timeconst_tests( 1973 bin: str, 1974 testdir: str, 1975 src_testdir: str, 1976) -> void 1977{ 1978 timeconst: str = path.join(testdir, "scripts/timeconst.bc"); 1979 1980 if !path.isfile(path.join(src_dir, timeconst)) 1981 { 1982 io.eprint("Warning: " +~ timeconst +~ " does not exist\n"); 1983 io.eprint(timeconst +~ " is not part of this bc because of " +~ 1984 "license incompatibility\n"); 1985 io.eprint("To test it, get it from the Linux kernel at " +~ 1986 "`kernel/time/timeconst.bc`\n"); 1987 io.eprint("Skipping...\n"); 1988 1989 return; 1990 } 1991 1992 for i: range(1001) 1993 { 1994 test sym(path.join(timeconst, str(i))) 1995 { 1996 idx: str = path.basename(tgt_name) +~ "\n"; 1997 file: str = path.join(src_dir, path.dirname(tgt_name)); 1998 1999 // Generate. 2000 res1 := $ bc -q $file << $idx; 2001 2002 if res1.exitcode != 0 2003 { 2004 io.eprint("Other bc is not GNU compatible. Skipping...\n"); 2005 return; 2006 } 2007 2008 // Run. 2009 res2 := $ %(config_list["args"]) -q $file << $idx; 2010 2011 if res2.exitcode != 0 || res2.stdout != res1.stdout 2012 { 2013 error("\nFailed on input: " +~ idx +~ "\n"); 2014 } 2015 } 2016 } 2017} 2018 2019fn register_history_tests( 2020 bin: str, 2021 testdir: str, 2022 src_testdir: str, 2023) -> void 2024{ 2025 calc: str = path.basename(testdir); 2026 2027 src_test_scriptdir: str = path.dirname(src_testdir); 2028 2029 len_res := $ @(path.join(src_test_scriptdir, "history.py")) $calc -a; 2030 2031 if len_res.exitcode != 0 2032 { 2033 io.eprint("Python 3 with pexpect doesn't work. Skipping history tests"); 2034 return; 2035 } 2036 2037 len: usize = usize(str(len_res.stdout)); 2038 2039 for i: range(len) 2040 { 2041 test sym(calc +~ "/history/" +~ str(i)): bin 2042 { 2043 name: str = tgt_name; 2044 parts: []str = name.split("/"); 2045 2046 calc: str = parts[0]; 2047 idx: str= parts[2]; 2048 2049 src_testdir: str = path.join(src_dir, "tests"); 2050 2051 $ @(path.join(src_testdir, "history.py")) -t $calc $idx @(file_dep); 2052 } 2053 } 2054} 2055 2056/** 2057 * Generates all of the test targets for an executable. 2058 * @param name The base name of the executable. 2059 * @param targets The targets that tests should depend on. 2060 */ 2061fn exe_tests(name: str) -> void 2062{ 2063 bin: str = exe_name(name); 2064 2065 testdir: str = path.join("tests", name); 2066 src_testdir: str = path.join(src_dir, testdir); 2067 2068 halt: str = if name == "bc" { "halt"; } else { "q"; }; 2069 gen_options: []str = if name == "bc" { @[ "-lq" ]; }; 2070 options: []str = if name == "bc" { @[ "-lqc" ]; } else { @[ "-xc" ]; }; 2071 2072 other_num: str = "10000000000000000000000000000000000000000000000000" +~ 2073 "0000000000000000000000000000"; 2074 other_num70: str = "10000000000000000000000000000000000000000000000" +~ 2075 "000000000000000000000\\\n0000000000"; 2076 2077 other_tests: []str = 2078 if name == "bc" 2079 { 2080 @[ "x", "extended-register", "line_length()", other_num ]; 2081 } 2082 else 2083 { 2084 @[ "l", "mathlib", "glpR", other_num +~ "pR" ]; 2085 }; 2086 2087 other_tests_results: []str = @[ other_num, other_num70 ]; 2088 2089 var: str = name.toupper() +~ "_LINE_LENGTH"; 2090 2091 script_options: []str = 2092 if name == "bc" 2093 { 2094 @[ "-lqC" ]; 2095 } 2096 else 2097 { 2098 @[ "-xC" ]; 2099 }; 2100 2101 error_options: []str = if name == "bc" { @[ "-ls" ]; } else { @[ "-x" ]; }; 2102 2103 args: []str = 2104 if bool(config["valgrind"]) 2105 { 2106 VALGRIND_ARGS +~ @[ "./" +~ bin ]; 2107 } 2108 else 2109 { 2110 @[ "./" +~ bin ]; 2111 }; 2112 2113 test_config: Gaml = @(gaml){ 2114 args: $args 2115 halt: $halt 2116 gen_options: $gen_options 2117 options: $options 2118 script_options: $script_options 2119 error_options: $error_options 2120 other_tests: $other_tests 2121 other_tests_results: $other_tests_results 2122 var: $var 2123 }; 2124 2125 push test_config: config_stack 2126 { 2127 extra: bool = bool(config["extra_math"]); 2128 2129 register_standard_tests(bin, testdir, src_testdir, extra); 2130 register_script_tests(bin, testdir, src_testdir, extra); 2131 register_stdin_tests(bin, testdir, src_testdir); 2132 register_read_tests(bin, testdir, src_testdir); 2133 register_error_tests(bin, testdir, src_testdir); 2134 register_other_tests(bin, testdir, src_testdir, extra); 2135 2136 if name == "bc" && bool(config["generated_tests"]) && 2137 path.isfile(path.join(src_testdir, "scripts/timeconst.bc")) 2138 { 2139 register_timeconst_tests(bin, testdir, src_testdir); 2140 } 2141 2142 if host.os != "Windows" && sym(config["history"]) == @builtin 2143 { 2144 register_history_tests(bin, testdir, src_testdir); 2145 } 2146 } 2147} 2148 2149/** 2150 * Gets the `$BINDIR`, including the `$DESTDIR`. This generates the default 2151 * value if it wasn't set. 2152 * @return The `$BINDIR`, with the `$DESTDIR`. 2153 */ 2154fn get_bindir() -> str 2155{ 2156 temp: str = str(config["bindir"]); 2157 2158 bindir: str = 2159 if temp == "" 2160 { 2161 path.join(str(config["prefix"]), "bin"); 2162 } 2163 else 2164 { 2165 temp; 2166 }; 2167 2168 return path.join(DESTDIR, bindir); 2169} 2170 2171/** 2172 * Gets the `$LIBDIR`, including the `$DESTDIR`. This generates the default 2173 * value if it wasn't set. 2174 * @return The `$LIBDIR`, with the `$DESTDIR`. 2175 */ 2176fn get_libdir() -> str 2177{ 2178 temp: str = str(config["libdir"]); 2179 2180 libdir: str = 2181 if temp == "" 2182 { 2183 path.join(str(config["prefix"]), "lib"); 2184 } 2185 else 2186 { 2187 temp; 2188 }; 2189 2190 return path.join(DESTDIR, libdir); 2191} 2192 2193/** 2194 * Gets the `$INCLUDEDIR`, including the `$DESTDIR`. This generates the default 2195 * value if it wasn't set. 2196 * @return The `$INCLUDEDIR`, with the `$DESTDIR`. 2197 */ 2198fn get_includedir() -> str 2199{ 2200 temp: str = str(config["includedir"]); 2201 2202 includedir: str = 2203 if temp == "" 2204 { 2205 path.join(str(config["prefix"]), "include"); 2206 } 2207 else 2208 { 2209 temp; 2210 }; 2211 2212 return path.join(DESTDIR, includedir); 2213} 2214 2215/** 2216 * Gets the `$PC_PATH`, including the `$DESTDIR`. This generates the default 2217 * value if it wasn't set. 2218 * @return The `$PC_PATH`, with the `$DESTDIR`. 2219 */ 2220fn get_pc_path() -> str 2221{ 2222 pc_path: str = 2223 if str(config["pc_path"]) == "" 2224 { 2225 res := $ pkg-config --variable=pc_path pkg-config; 2226 2227 str(res.stdout); 2228 } 2229 else 2230 { 2231 str(config["pc_path"]); 2232 }; 2233 2234 return path.join(DESTDIR, pc_path); 2235} 2236 2237/** 2238 * Gets the `$DATAROOTDIR`, including the `$DESTDIR`. This generates the default 2239 * value if it wasn't set. 2240 * @return The `$DATAROOTDIR`, with the `$DESTDIR`. 2241 */ 2242fn get_datarootdir() -> str 2243{ 2244 temp: str = str(config["datarootdir"]); 2245 2246 datarootdir: str = 2247 if temp == "" 2248 { 2249 path.join(str(config["prefix"]), "share"); 2250 } 2251 else 2252 { 2253 temp; 2254 }; 2255 2256 return path.join(DESTDIR, datarootdir); 2257} 2258 2259/** 2260 * Gets the `$DATADIR`, including the `$DESTDIR`. This generates the default 2261 * value if it wasn't set. 2262 * @return The `$DATADIR`, with the `$DESTDIR`. 2263 */ 2264fn get_datadir() -> str 2265{ 2266 temp: str = str(config["datadir"]); 2267 2268 datadir: str = 2269 if temp == "" 2270 { 2271 get_datarootdir(); 2272 } 2273 else 2274 { 2275 temp; 2276 }; 2277 2278 return path.join(DESTDIR, datadir); 2279} 2280 2281/** 2282 * Gets the `$MANDIR`, including the `$DESTDIR`. This generates the default 2283 * value if it wasn't set. 2284 * @return The `$MANDIR`, with the `$DESTDIR`. 2285 */ 2286fn get_mandir() -> str 2287{ 2288 temp: str = str(config["mandir"]); 2289 2290 mandir: str = 2291 if temp == "" 2292 { 2293 path.join(get_datadir(), "man"); 2294 } 2295 else 2296 { 2297 temp; 2298 }; 2299 2300 return path.join(DESTDIR, mandir); 2301} 2302 2303/** 2304 * Gets the `$MAN1DIR`, including the `$DESTDIR`. This generates the default 2305 * value if it wasn't set. 2306 * @return The `$MAN1DIR`, with the `$DESTDIR`. 2307 */ 2308fn get_man1dir() -> str 2309{ 2310 temp: str = str(config["man1dir"]); 2311 2312 man1dir: str = 2313 if temp == "" 2314 { 2315 path.join(get_mandir(), "man1"); 2316 } 2317 else 2318 { 2319 temp; 2320 }; 2321 2322 return path.join(DESTDIR, man1dir); 2323} 2324 2325/** 2326 * Gets the `$MAN3DIR`, including the `$DESTDIR`. This generates the default 2327 * value if it wasn't set. 2328 * @return The `$MAN3DIR`, with the `$DESTDIR`. 2329 */ 2330fn get_man3dir() -> str 2331{ 2332 temp: str = str(config["man3dir"]); 2333 2334 man3dir: str = 2335 if temp == "" 2336 { 2337 path.join(get_mandir(), "man3"); 2338 } 2339 else 2340 { 2341 temp; 2342 }; 2343 2344 return path.join(DESTDIR, man3dir); 2345} 2346