/usr/share/tcltk/critcl3.1.8/critcl.tcl is in critcl 3.1.9-1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 2282 2283 2284 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 2345 2346 2347 2348 2349 2350 2351 2352 2353 2354 2355 2356 2357 2358 2359 2360 2361 2362 2363 2364 2365 2366 2367 2368 2369 2370 2371 2372 2373 2374 2375 2376 2377 2378 2379 2380 2381 2382 2383 2384 2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399 2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426 2427 2428 2429 2430 2431 2432 2433 2434 2435 2436 2437 2438 2439 2440 2441 2442 2443 2444 2445 2446 2447 2448 2449 2450 2451 2452 2453 2454 2455 2456 2457 2458 2459 2460 2461 2462 2463 2464 2465 2466 2467 2468 2469 2470 2471 2472 2473 2474 2475 2476 2477 2478 2479 2480 2481 2482 2483 2484 2485 2486 2487 2488 2489 2490 2491 2492 2493 2494 2495 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 2506 2507 2508 2509 2510 2511 2512 2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525 2526 2527 2528 2529 2530 2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 2545 2546 2547 2548 2549 2550 2551 2552 2553 2554 2555 2556 2557 2558 2559 2560 2561 2562 2563 2564 2565 2566 2567 2568 2569 2570 2571 2572 2573 2574 2575 2576 2577 2578 2579 2580 2581 2582 2583 2584 2585 2586 2587 2588 2589 2590 2591 2592 2593 2594 2595 2596 2597 2598 2599 2600 2601 2602 2603 2604 2605 2606 2607 2608 2609 2610 2611 2612 2613 2614 2615 2616 2617 2618 2619 2620 2621 2622 2623 2624 2625 2626 2627 2628 2629 2630 2631 2632 2633 2634 2635 2636 2637 2638 2639 2640 2641 2642 2643 2644 2645 2646 2647 2648 2649 2650 2651 2652 2653 2654 2655 2656 2657 2658 2659 2660 2661 2662 2663 2664 2665 2666 2667 2668 2669 2670 2671 2672 2673 2674 2675 2676 2677 2678 2679 2680 2681 2682 2683 2684 2685 2686 2687 2688 2689 2690 2691 2692 2693 2694 2695 2696 2697 2698 2699 2700 2701 2702 2703 2704 2705 2706 2707 2708 2709 2710 2711 2712 2713 2714 2715 2716 2717 2718 2719 2720 2721 2722 2723 2724 2725 2726 2727 2728 2729 2730 2731 2732 2733 2734 2735 2736 2737 2738 2739 2740 2741 2742 2743 2744 2745 2746 2747 2748 2749 2750 2751 2752 2753 2754 2755 2756 2757 2758 2759 2760 2761 2762 2763 2764 2765 2766 2767 2768 2769 2770 2771 2772 2773 2774 2775 2776 2777 2778 2779 2780 2781 2782 2783 2784 2785 2786 2787 2788 2789 2790 2791 2792 2793 2794 2795 2796 2797 2798 2799 2800 2801 2802 2803 2804 2805 2806 2807 2808 2809 2810 2811 2812 2813 2814 2815 2816 2817 2818 2819 2820 2821 2822 2823 2824 2825 2826 2827 2828 2829 2830 2831 2832 2833 2834 2835 2836 2837 2838 2839 2840 2841 2842 2843 2844 2845 2846 2847 2848 2849 2850 2851 2852 2853 2854 2855 2856 2857 2858 2859 2860 2861 2862 2863 2864 2865 2866 2867 2868 2869 2870 2871 2872 2873 2874 2875 2876 2877 2878 2879 2880 2881 2882 2883 2884 2885 2886 2887 2888 2889 2890 2891 2892 2893 2894 2895 2896 2897 2898 2899 2900 2901 2902 2903 2904 2905 2906 2907 2908 2909 2910 2911 2912 2913 2914 2915 2916 2917 2918 2919 2920 2921 2922 2923 2924 2925 2926 2927 2928 2929 2930 2931 2932 2933 2934 2935 2936 2937 2938 2939 2940 2941 2942 2943 2944 2945 2946 2947 2948 2949 2950 2951 2952 2953 2954 2955 2956 2957 2958 2959 2960 2961 2962 2963 2964 2965 2966 2967 2968 2969 2970 2971 2972 2973 2974 2975 2976 2977 2978 2979 2980 2981 2982 2983 2984 2985 2986 2987 2988 2989 2990 2991 2992 2993 2994 2995 2996 2997 2998 2999 3000 3001 3002 3003 3004 3005 3006 3007 3008 3009 3010 3011 3012 3013 3014 3015 3016 3017 3018 3019 3020 3021 3022 3023 3024 3025 3026 3027 3028 3029 3030 3031 3032 3033 3034 3035 3036 3037 3038 3039 3040 3041 3042 3043 3044 3045 3046 3047 3048 3049 3050 3051 3052 3053 3054 3055 3056 3057 3058 3059 3060 3061 3062 3063 3064 3065 3066 3067 3068 3069 3070 3071 3072 3073 3074 3075 3076 3077 3078 3079 3080 3081 3082 3083 3084 3085 3086 3087 3088 3089 3090 3091 3092 3093 3094 3095 3096 3097 3098 3099 3100 3101 3102 3103 3104 3105 3106 3107 3108 3109 3110 3111 3112 3113 3114 3115 3116 3117 3118 3119 3120 3121 3122 3123 3124 3125 3126 3127 3128 3129 3130 3131 3132 3133 3134 3135 3136 3137 3138 3139 3140 3141 3142 3143 3144 3145 3146 3147 3148 3149 3150 3151 3152 3153 3154 3155 3156 3157 3158 3159 3160 3161 3162 3163 3164 3165 3166 3167 3168 3169 3170 3171 3172 3173 3174 3175 3176 3177 3178 3179 3180 3181 3182 3183 3184 3185 3186 3187 3188 3189 3190 3191 3192 3193 3194 3195 3196 3197 3198 3199 3200 3201 3202 3203 3204 3205 3206 3207 3208 3209 3210 3211 3212 3213 3214 3215 3216 3217 3218 3219 3220 3221 3222 3223 3224 3225 3226 3227 3228 3229 3230 3231 3232 3233 3234 3235 3236 3237 3238 3239 3240 3241 3242 3243 3244 3245 3246 3247 3248 3249 3250 3251 3252 3253 3254 3255 3256 3257 3258 3259 3260 3261 3262 3263 3264 3265 3266 3267 3268 3269 3270 3271 3272 3273 3274 3275 3276 3277 3278 3279 3280 3281 3282 3283 3284 3285 3286 3287 3288 3289 3290 3291 3292 3293 3294 3295 3296 3297 3298 3299 3300 3301 3302 3303 3304 3305 3306 3307 3308 3309 3310 3311 3312 3313 3314 3315 3316 3317 3318 3319 3320 3321 3322 3323 3324 3325 3326 3327 3328 3329 3330 3331 3332 3333 3334 3335 3336 3337 3338 3339 3340 3341 3342 3343 3344 3345 3346 3347 3348 3349 3350 3351 3352 3353 3354 3355 3356 3357 3358 3359 3360 3361 3362 3363 3364 3365 3366 3367 3368 3369 3370 3371 3372 3373 3374 3375 3376 3377 3378 3379 3380 3381 3382 3383 3384 3385 3386 3387 3388 3389 3390 3391 3392 3393 3394 3395 3396 3397 3398 3399 3400 3401 3402 3403 3404 3405 3406 3407 3408 3409 3410 3411 3412 3413 3414 3415 3416 3417 3418 3419 3420 3421 3422 3423 3424 3425 3426 3427 3428 3429 3430 3431 3432 3433 3434 3435 3436 3437 3438 3439 3440 3441 3442 3443 3444 3445 3446 3447 3448 3449 3450 3451 3452 3453 3454 3455 3456 3457 3458 3459 3460 3461 3462 3463 3464 3465 3466 3467 3468 3469 3470 3471 3472 3473 3474 3475 3476 3477 3478 3479 3480 3481 3482 3483 3484 3485 3486 3487 3488 3489 3490 3491 3492 3493 3494 3495 3496 3497 3498 3499 3500 3501 3502 3503 3504 3505 3506 3507 3508 3509 3510 3511 3512 3513 3514 3515 3516 3517 3518 3519 3520 3521 3522 3523 3524 3525 3526 3527 3528 3529 3530 3531 3532 3533 3534 3535 3536 3537 3538 3539 3540 3541 3542 3543 3544 3545 3546 3547 3548 3549 3550 3551 3552 3553 3554 3555 3556 3557 3558 3559 3560 3561 3562 3563 3564 3565 3566 3567 3568 3569 3570 3571 3572 3573 3574 3575 3576 3577 3578 3579 3580 3581 3582 3583 3584 3585 3586 3587 3588 3589 3590 3591 3592 3593 3594 3595 3596 3597 3598 3599 3600 3601 3602 3603 3604 3605 3606 3607 3608 3609 3610 3611 3612 3613 3614 3615 3616 3617 3618 3619 3620 3621 3622 3623 3624 3625 3626 3627 3628 3629 3630 3631 3632 3633 3634 3635 3636 3637 3638 3639 3640 3641 3642 3643 3644 3645 3646 3647 3648 3649 3650 3651 3652 3653 3654 3655 3656 3657 3658 3659 3660 3661 3662 3663 3664 3665 3666 3667 3668 3669 3670 3671 3672 3673 3674 3675 3676 3677 3678 3679 3680 3681 3682 3683 3684 3685 3686 3687 3688 3689 3690 3691 3692 3693 3694 3695 3696 3697 3698 3699 3700 3701 3702 3703 3704 3705 3706 3707 3708 3709 3710 3711 3712 3713 3714 3715 3716 3717 3718 3719 3720 3721 3722 3723 3724 3725 3726 3727 3728 3729 3730 3731 3732 3733 3734 3735 3736 3737 3738 3739 3740 3741 3742 3743 3744 3745 3746 3747 3748 3749 3750 3751 3752 3753 3754 3755 3756 3757 3758 3759 3760 3761 3762 3763 3764 3765 3766 3767 3768 3769 3770 3771 3772 3773 3774 3775 3776 3777 3778 3779 3780 3781 3782 3783 3784 3785 3786 3787 3788 3789 3790 3791 3792 3793 3794 3795 3796 3797 3798 3799 3800 3801 3802 3803 3804 3805 3806 3807 3808 3809 3810 3811 3812 3813 3814 3815 3816 3817 3818 3819 3820 3821 3822 3823 3824 3825 3826 3827 3828 3829 3830 3831 3832 3833 3834 3835 3836 3837 3838 3839 3840 3841 3842 3843 3844 3845 3846 3847 3848 3849 3850 3851 3852 3853 3854 3855 3856 3857 3858 3859 3860 3861 3862 3863 3864 3865 3866 3867 3868 3869 3870 3871 3872 3873 3874 3875 3876 3877 3878 3879 3880 3881 3882 3883 3884 3885 3886 3887 3888 3889 3890 3891 3892 3893 3894 3895 3896 3897 3898 3899 3900 3901 3902 3903 3904 3905 3906 3907 3908 3909 3910 3911 3912 3913 3914 3915 3916 3917 3918 3919 3920 3921 3922 3923 3924 3925 3926 3927 3928 3929 3930 3931 3932 3933 3934 3935 3936 3937 3938 3939 3940 3941 3942 3943 3944 3945 3946 3947 3948 3949 3950 3951 3952 3953 3954 3955 3956 3957 3958 3959 3960 3961 3962 3963 3964 3965 3966 3967 3968 3969 3970 3971 3972 3973 3974 3975 3976 3977 3978 3979 3980 3981 3982 3983 3984 3985 3986 3987 3988 3989 3990 3991 3992 3993 3994 3995 3996 3997 3998 3999 4000 4001 4002 4003 4004 4005 4006 4007 4008 4009 4010 4011 4012 4013 4014 4015 4016 4017 4018 4019 4020 4021 4022 4023 4024 4025 4026 4027 4028 4029 4030 4031 4032 4033 4034 4035 4036 4037 4038 4039 4040 4041 4042 4043 4044 4045 4046 4047 4048 4049 4050 4051 4052 4053 4054 4055 4056 4057 4058 4059 4060 4061 4062 4063 4064 4065 4066 4067 4068 4069 4070 4071 4072 4073 4074 4075 4076 4077 4078 4079 4080 4081 4082 4083 4084 4085 4086 4087 4088 4089 4090 4091 4092 4093 4094 4095 4096 4097 4098 4099 4100 4101 4102 4103 4104 4105 4106 4107 4108 4109 4110 4111 4112 4113 4114 4115 4116 4117 4118 4119 4120 4121 4122 4123 4124 4125 4126 4127 4128 4129 4130 4131 4132 4133 4134 4135 4136 4137 4138 4139 4140 4141 4142 4143 4144 4145 4146 4147 4148 4149 4150 4151 4152 4153 4154 4155 4156 4157 4158 4159 4160 4161 4162 4163 4164 4165 4166 4167 4168 4169 4170 4171 4172 4173 4174 4175 4176 4177 4178 4179 4180 4181 4182 4183 4184 4185 4186 4187 4188 4189 4190 4191 4192 4193 4194 4195 4196 4197 4198 4199 4200 4201 4202 4203 4204 4205 4206 4207 4208 4209 4210 4211 4212 4213 4214 4215 4216 4217 4218 4219 4220 4221 4222 4223 4224 4225 4226 4227 4228 4229 4230 4231 4232 4233 4234 4235 4236 4237 4238 4239 4240 4241 4242 4243 4244 4245 4246 4247 4248 4249 4250 4251 4252 4253 4254 4255 4256 4257 4258 4259 4260 4261 4262 4263 4264 4265 4266 4267 4268 4269 4270 4271 4272 4273 4274 4275 4276 4277 4278 4279 4280 4281 4282 4283 4284 4285 4286 4287 4288 4289 4290 4291 4292 4293 4294 4295 4296 4297 4298 4299 4300 4301 4302 4303 4304 4305 4306 4307 4308 4309 4310 4311 4312 4313 4314 4315 4316 4317 4318 4319 4320 4321 4322 4323 4324 4325 4326 4327 4328 4329 4330 4331 4332 4333 4334 4335 4336 4337 4338 4339 4340 4341 4342 4343 4344 4345 4346 4347 4348 4349 4350 4351 4352 4353 4354 4355 4356 4357 4358 4359 4360 4361 4362 4363 4364 4365 4366 4367 4368 4369 4370 4371 4372 4373 4374 4375 4376 4377 4378 4379 4380 4381 4382 4383 4384 4385 4386 4387 4388 4389 4390 4391 4392 4393 4394 4395 4396 4397 4398 4399 4400 4401 4402 4403 4404 4405 4406 4407 4408 4409 4410 4411 4412 4413 4414 4415 4416 4417 4418 4419 4420 4421 4422 4423 4424 4425 4426 4427 4428 4429 4430 4431 4432 4433 4434 4435 4436 4437 4438 4439 4440 4441 4442 4443 4444 4445 4446 4447 4448 4449 4450 4451 4452 4453 4454 4455 4456 4457 4458 4459 4460 4461 4462 4463 4464 4465 4466 4467 4468 4469 4470 4471 4472 4473 4474 4475 4476 4477 4478 4479 4480 4481 4482 4483 4484 4485 4486 4487 4488 4489 4490 4491 4492 4493 4494 4495 4496 4497 4498 4499 4500 4501 4502 4503 4504 4505 4506 4507 4508 4509 4510 4511 4512 4513 4514 4515 4516 4517 4518 4519 4520 4521 4522 4523 4524 4525 4526 4527 4528 4529 4530 4531 4532 4533 4534 4535 4536 4537 4538 4539 4540 4541 4542 4543 4544 4545 4546 4547 4548 4549 4550 4551 4552 4553 4554 4555 4556 4557 4558 4559 4560 4561 4562 4563 4564 4565 4566 4567 4568 4569 4570 4571 4572 4573 4574 4575 4576 4577 4578 4579 4580 4581 4582 4583 4584 4585 4586 4587 4588 4589 4590 4591 4592 4593 4594 4595 4596 4597 4598 4599 4600 4601 4602 4603 4604 4605 4606 4607 4608 4609 4610 4611 4612 4613 4614 4615 4616 4617 4618 4619 4620 4621 4622 4623 4624 4625 4626 4627 4628 4629 4630 4631 4632 4633 4634 4635 4636 4637 4638 4639 4640 4641 4642 4643 4644 4645 4646 4647 4648 4649 4650 4651 4652 4653 4654 4655 4656 4657 4658 4659 4660 4661 4662 4663 4664 4665 4666 4667 4668 4669 4670 4671 4672 4673 4674 4675 4676 4677 4678 4679 4680 4681 4682 4683 4684 4685 4686 4687 4688 4689 4690 4691 4692 4693 4694 4695 4696 4697 4698 4699 4700 4701 4702 4703 4704 4705 4706 4707 4708 4709 4710 4711 4712 4713 4714 4715 4716 4717 4718 4719 4720 4721 4722 4723 4724 4725 4726 4727 4728 4729 4730 4731 4732 4733 4734 4735 4736 4737 4738 4739 4740 4741 4742 4743 4744 4745 4746 4747 4748 4749 4750 4751 4752 4753 4754 4755 4756 4757 4758 4759 4760 4761 4762 4763 4764 4765 4766 4767 4768 4769 4770 4771 4772 4773 4774 4775 4776 4777 4778 4779 4780 4781 4782 4783 4784 4785 4786 4787 4788 4789 4790 4791 4792 4793 4794 4795 4796 4797 4798 4799 4800 4801 4802 4803 4804 4805 4806 4807 4808 4809 4810 4811 4812 4813 4814 4815 4816 4817 4818 4819 4820 4821 4822 4823 4824 4825 4826 4827 4828 4829 4830 4831 4832 4833 4834 4835 4836 4837 4838 4839 4840 4841 4842 4843 4844 4845 4846 4847 4848 4849 4850 4851 4852 4853 4854 4855 4856 4857 4858 4859 4860 4861 4862 4863 4864 4865 4866 4867 4868 4869 4870 4871 4872 4873 4874 4875 4876 4877 4878 4879 4880 4881 4882 4883 4884 4885 4886 4887 4888 4889 4890 4891 4892 4893 4894 4895 4896 4897 4898 4899 4900 4901 4902 4903 4904 4905 4906 4907 4908 4909 4910 4911 4912 4913 4914 4915 4916 4917 4918 4919 4920 4921 4922 4923 4924 4925 4926 4927 4928 4929 4930 4931 4932 4933 4934 4935 4936 4937 4938 4939 4940 4941 4942 4943 4944 4945 4946 4947 4948 4949 4950 4951 4952 4953 4954 4955 4956 4957 4958 4959 4960 4961 4962 4963 4964 4965 4966 4967 4968 4969 4970 4971 4972 4973 4974 4975 4976 4977 4978 4979 4980 4981 4982 4983 4984 4985 4986 4987 4988 4989 4990 4991 4992 4993 4994 4995 4996 4997 4998 4999 5000 5001 5002 5003 5004 5005 5006 5007 5008 5009 5010 5011 5012 5013 5014 5015 5016 5017 5018 5019 5020 5021 5022 5023 5024 5025 5026 5027 5028 5029 5030 5031 5032 5033 5034 5035 5036 5037 5038 5039 5040 5041 5042 5043 5044 5045 5046 5047 5048 5049 5050 5051 5052 5053 5054 5055 5056 5057 5058 5059 5060 5061 5062 5063 5064 5065 5066 5067 5068 5069 5070 5071 5072 5073 5074 5075 5076 5077 5078 5079 5080 5081 5082 5083 5084 5085 5086 5087 5088 5089 5090 5091 5092 5093 5094 5095 5096 5097 5098 5099 5100 5101 5102 5103 5104 5105 5106 5107 5108 5109 5110 5111 5112 5113 5114 5115 5116 5117 5118 5119 5120 5121 5122 5123 5124 5125 5126 5127 5128 5129 5130 5131 5132 5133 5134 5135 5136 5137 5138 5139 5140 5141 5142 5143 5144 5145 5146 5147 5148 5149 | ## -*- tcl -*-
# # ## ### ##### ######## ############# #####################
# Pragmas for MetaData Scanner.
# @mdgen OWNER: Config
# @mdgen OWNER: critcl_c
# CriTcl Core.
package provide critcl 3.1.8
# # ## ### ##### ######## ############# #####################
## Requirements.
package require Tcl 8.4 ; # Minimal supported Tcl runtime.
if {[catch {
package require platform 1.0.2 ; # Determine current platform.
}]} {
# Fall back to our internal copy (currently at platform 1.0.11
# equivalent) if the environment does not have the official
# package.
package require critcl::platform
}
# Ensure forward compatibility of commands defined in 8.5+.
package require lassign84
package require dict84
catch { interp debug {} -frame 1 }
# md5 could be a cmd or a pkg, or be in a separate namespace
if {[catch { md5 "" }]} {
# Do *not* use "package require md5c" since critcl is not loaded
# yet, but do look for a compiled one, in case object code already
# exists.
if {![catch { md5c "" }]} {
interp alias {} md5 {} md5c
} elseif {[catch {package require Trf 2.0}] || [catch {::md5 -- test}]} {
# Else try to load the Tcl version in tcllib
catch { package require md5 }
if {![catch { md5::md5 "" }]} {
interp alias {} md5 {} md5::md5
} else {
# Last resort: package require or source Don Libes'
# md5pure script
if {[catch { package require md5pure }]} {
if {[file exists md5pure.tcl]} {
source md5pure.tcl
interp alias {} md5 {} md5pure::md5
} else {
# XXX: Note the assumption here, that the md5
# XXX: package is found relative to critcl itself,
# XXX: in the critcl starkit.
source [file join [file dirname [info script]] ../md5/md5.tcl]
interp alias {} md5 {} md5::md5
}
} else {
interp alias {} md5 {} md5pure::md5
}
}
}
}
namespace eval ::critcl {}
# ouch, some md5 implementations return hex, others binary
if {[string length [md5 ""]] == 32} {
proc ::critcl::md5_hex {s} { return [md5 $s] }
} else {
proc ::critcl::md5_hex {s} { binary scan [md5 $s] H* md; return $md }
}
# # ## ### ##### ######## ############# #####################
if {[package vsatisfies [package present Tcl] 8.5]} {
# 8.5+
proc ::critcl::lappendlist {lvar list} {
if {![llength $list]} return
upvar $lvar dest
lappend dest {*}$list
return
}
} else {
# 8.4
proc ::critcl::lappendlist {lvar list} {
if {![llength $list]} return
upvar $lvar dest
set dest [eval [linsert $list 0 linsert $dest end]]
#set dest [concat $dest $list]
return
}
}
# # ## ### ##### ######## ############# #####################
##
proc ::critcl::buildrequirement {script} {
# In regular code this does nothing. It is a marker for
# the static scanner to change under what key to record
# the 'package require' found in the script.
uplevel 1 $script
}
proc ::critcl::TeapotPlatform {} {
# Platform identifier HACK. Most of the data in critcl is based on
# 'platform::generic'. The TEApot MD however uses
# 'platform::identify' with its detail information (solaris kernel
# version, linux glibc version). But, if a cross-compile is
# running we are SOL, because we have no place to pull the
# necessary detail from, 'identify' is a purely local operation :(
set platform [actualtarget]
if {[platform::generic] eq $platform} {
set platform [platform::identify]
}
return $platform
}
proc ::critcl::TeapotRequire {dspec} {
# Syntax of dspec: (a) pname
# ...: (b) pname req-version...
# ...: (c) pname -exact req-version
#
# We can assume that the syntax is generally ok, because otherwise
# the 'package require' itself will fail in a moment, blocking the
# further execution of the .critcl file. So we only have to
# distinguish the cases.
if {([llength $dspec] == 3) &&
([lindex $dspec 1] eq "-exact")} {
# (c)
lassign $dspec pn _ pv
set spec [list $pn ${pv}-$pv]
} else {
# (a, b)
set spec $dspec
}
return $spec
}
# # ## ### ##### ######## ############# #####################
## Implementation -- API: Embed C Code
proc ::critcl::HeaderLines {text} {
if {![regexp {^[\t\n ]+} $text header]} {
return [list 0 $text]
}
set lines [regexp -all {\n} $header]
# => The C code begins $lines lines after location of the c**
# command. This goes as offset into the generated #line pragma,
# because now (see next line) we throw away this leading
# whitespace.
set text [string trim $text]
return [list $lines $text]
}
proc ::critcl::Lines {text} {
set n [regexp -all {\n} $text]
return $n
}
proc ::critcl::ccode {text} {
set file [SkipIgnored [This]]
AbortWhenCalledAfterBuild
set digest [UUID.extend $file .ccode $text]
set block {}
lassign [HeaderLines $text] leadoffset text
append block [at::CPragma $leadoffset -2 $file] $text \n
dict update v::code($file) config c {
dict lappend c fragments $digest
dict set c block $digest $block
dict lappend c defs $digest
}
return
}
proc ::critcl::ccommand {name anames args} {
SkipIgnored [set file [This]]
AbortWhenCalledAfterBuild
if {[llength $args]} {
set body [lindex $args 0]
set args [lrange $args 1 end]
} else {
set body {}
}
set clientdata NULL
set delproc 0
set acname 0
while {[string match "-*" $args]} {
switch -- [set opt [lindex $args 0]] {
-clientdata { set clientdata [lindex $args 1] }
-delproc { set delproc [lindex $args 1] }
-cname { set acname [lindex $args 1] }
default {
error "Unknown option $opt, expected one of -clientdata, -cname, or -delproc"
}
}
set args [lrange $args 2 end]
}
if {$acname} {
BeginCommand static $name $anames $args
set ns {}
set cns {}
set key $name
set wname $name
} else {
lassign [BeginCommand public $name $anames $args] ns cns name cname
set key [string map {:: _} $ns$name]
set wname tcl_$cns$cname
}
# XXX clientdata/delproc, either note clashes, or keep information per-file.
set v::clientdata($key) $clientdata
set v::delproc($key) $delproc
#set body [join $args]
if {$body != ""} {
lappend anames ""
foreach {cd ip oc ov} $anames break
if {$cd eq ""} { set cd clientdata }
if {$ip eq ""} { set ip interp }
if {$oc eq ""} { set oc objc }
if {$ov eq ""} { set ov objv }
set ca "(ClientData $cd, Tcl_Interp *$ip, int $oc, Tcl_Obj *CONST $ov\[])"
Emitln "static int $wname$ca"
Emit \{\n
lassign [HeaderLines $body] leadoffset body
if {$v::options(lines)} {
Emit [at::CPragma $leadoffset -2 $file]
}
Emit $body
Emitln \n\}
} else {
# if no body is specified, then $anames is alias for the real cmd proc
Emitln "#define $wname $anames"
Emitln "int $anames\(\);"
}
EndCommand
return
}
proc ::critcl::cdata {name data} {
SkipIgnored [This]
AbortWhenCalledAfterBuild
binary scan $data c* bytes ;# split as bytes, not (unicode) chars
set inittext ""
set line ""
foreach x $bytes {
if {[string length $line] > 70} {
append inittext " " $line \n
set line ""
}
append line $x ,
}
append inittext " " $line
set count [llength $bytes]
set body [subst [Cat [Template cdata.c]]]
# ^=> count, inittext
# NOTE: The uplevel is needed because otherwise 'ccommand' will
# not properly determine the caller's namespace.
uplevel 1 [list critcl::ccommand $name {dummy ip objc objv} [at::caller!]$body]
return $name
}
proc ::critcl::cdefines {defines {namespace "::"}} {
set file [SkipIgnored [This]]
AbortWhenCalledAfterBuild
set digest [UUID.extend $file .cdefines [list $defines $namespace]]
dict update v::code($file) config c {
foreach def $defines {
dict set c const $def $namespace
}
}
return
}
proc ::critcl::argoptional {adefs} {
set optional {}
# A 1st argument matching "Tcl_Interp*" does not count as a user
# visible command argument.
if {[lindex $adefs 0] eq "Tcl_Interp*"} {
set adefs [lrange $adefs 2 end]
}
foreach {t a} $adefs {
if {[llength $a] == 2} {
lappend optional 1
} else {
lappend optional 0
}
}
return $optional
}
proc ::critcl::argdefaults {adefs} {
set defaults {}
# A 1st argument matching "Tcl_Interp*" does not count as a user
# visible command argument.
if {[lindex $adefs 0] eq "Tcl_Interp*"} {
set adefs [lrange $adefs 2 end]
}
foreach {t a} $adefs {
if {[llength $a] == 2} {
lappend defaults [lindex $a 1]
}
}
return $defaults
}
proc ::critcl::argnames {adefs} {
set names {}
# A 1st argument matching "Tcl_Interp*" does not count as a user
# visible command argument.
if {[lindex $adefs 0] eq "Tcl_Interp*"} {
set adefs [lrange $adefs 2 end]
}
foreach {t a} $adefs {
if {[llength $a] == 2} {
set a [lindex $a 0]
}
lappend names $a
}
return $names
}
proc ::critcl::argcnames {adefs {interp ip}} {
set cnames {}
if {[lindex $adefs 0] eq "Tcl_Interp*"} {
lappend cnames interp
set adefs [lrange $adefs 2 end]
}
foreach {t a} $adefs {
if {[llength $a] == 2} {
set a [lindex $a 0]
}
lappend cnames _$a
}
return $cnames
}
proc ::critcl::argcsignature {adefs} {
# Construct the signature of the low-level C function.
set cargs {}
# If the 1st argument is "Tcl_Interp*", we pass it without
# counting it as a command argument.
if {[lindex $adefs 0] eq "Tcl_Interp*"} {
lappend cargs [lrange $adefs 0 1]
set adefs [lrange $adefs 2 end]
}
foreach {t a} $adefs {
if {[llength $a] == 2} {
set a [lindex $a 0]
}
lappend cargs "[ArgumentCTypeB $t] $a"
}
return $cargs
}
proc ::critcl::argvardecls {adefs} {
# Argument variables, destinations for the Tcl -> C conversion.
# A 1st argument matching "Tcl_Interp*" does not count as a user
# visible command argument.
if {[lindex $adefs 0] eq "Tcl_Interp*"} {
set adefs [lrange $adefs 2 end]
}
set result {}
foreach {t a} $adefs {
if {[llength $a] == 2} {
set a [lindex $a 0]
}
lappend result "[ArgumentCType $t] _$a;"
}
return $result
}
proc ::critcl::argsupport {adefs} {
# Argument global support, outside/before function.
# A 1st argument matching "Tcl_Interp*" does not count as a user
# visible command argument.
if {[lindex $adefs 0] eq "Tcl_Interp*"} {
set adefs [lrange $adefs 2 end]
}
set has {}
set result {}
foreach {t a} $adefs {
if {[lsearch -exact $has $t] >= 0} continue
lappend has $t
lappend result "[ArgumentSupport $t]"
}
return $result
}
proc ::critcl::argconversion {adefs {n 1}} {
# A 1st argument matching "Tcl_Interp*" does not count as a user
# visible command argument.
if {[lindex $adefs 0] eq "Tcl_Interp*"} {
set adefs [lrange $adefs 2 end]
}
set min $n ; # count all non-optional arguments. min required.
foreach {t a} $adefs {
if {[llength $a] == 2} continue
incr min
}
set result {}
set opt 0
set prefix " idx_ = $n;\n"
foreach {t a} $adefs {
if {[llength $a] == 2} {
# Optional argument. Can be first, or later.
# For the first the prefix gives us the code to initialize idx_.
lassign $a a default
set map [list @@ "ov\[idx_\]" @A _$a]
set code [string map $map [ArgumentConversion $t]]
set code "${prefix} if (oc > $min) \{\n$code\n idx_++;\n \} else \{\n _$a = $default;\n \}"
incr min
lappend result " /* ($t $a, optional, default $default) - - -- --- ----- -------- */"
lappend result $code
lappend result {}
set opt 1
set prefix ""
} elseif {$opt} {
# Fixed argument, after the optionals.
# Main issue: Use idx_ to access the array.
# We know that no optionals can follow, only the same.
set map [list @@ "ov\[idx_\]" @A _$a]
lappend result " /* ($t $a) - - -- --- ----- -------- */"
lappend result [string map $map [ArgumentConversion $t]]
lappend result " idx_++;"
lappend result {}
} else {
# Fixed argument, before any optionals.
set map [list @@ "ov\[$n\]" @A _$a]
lappend result " /* ($t $a) - - -- --- ----- -------- */"
lappend result [string map $map [ArgumentConversion $t]]
lappend result {}
incr n
set prefix " idx_ = $n;\n"
}
}
return $result
}
proc ::critcl::argtype {name conversion {ctype {}} {ctypeb {}}} {
variable v::actype
variable v::actypeb
variable v::aconv
# ctype Type of variable holding the argument.
# ctypeb Type of formal C function argument.
if {[info exists aconv($name)]} {
return -code error "Illegal duplicate definition of '$name'."
}
# Handle aliases by copying the original definition.
if {$conversion eq "="} {
if {![info exists aconv($ctype)]} {
return -code error "Unable to alias unknown type '$ctype'."
}
set conversion $aconv($ctype)
set ctypeb $actypeb($ctype)
set ctype $actype($ctype)
} else {
lassign [HeaderLines $conversion] leadoffset conversion
set conversion "\t\{\n[at::caller! $leadoffset]\t[string trim $conversion] \}"
}
if {$ctype eq {}} {
set ctype $name
}
if {$ctypeb eq {}} {
set ctypeb $name
}
set aconv($name) $conversion
set actype($name) $ctype
set actypeb($name) $ctypeb
return
}
proc ::critcl::argtypesupport {name code} {
variable v::aconv
variable v::acsup
if {![info exists aconv($name)]} {
return -code error "No definition for '$name'."
}
if {[info exists acsup($name)]} {
return -code error "Illegal duplicate support of '$name'."
}
lappend lines "#ifndef CRITCL_$name"
lappend lines "#define CRITCL_$name"
lappend lines $code
lappend lines "#endif"
set acsup($name) [join $lines \n]
return
}
proc ::critcl::resulttype {name conversion {ctype {}}} {
variable v::rctype
variable v::rconv
if {[info exists rconv($name)]} {
return -code error "Illegal duplicate definition of '$name'."
}
# Handle aliases by copying the original definition.
if {$conversion eq "="} {
if {![info exists rconv($ctype)]} {
return -code error "Unable to alias unknown type '$ctype'."
}
set conversion $rconv($ctype)
set ctype $rctype($ctype)
} else {
lassign [HeaderLines $conversion] leadoffset conversion
set conversion [at::caller! $leadoffset]\t[string trimright $conversion]
}
if {$ctype eq {}} {
set ctype $name
}
set rconv($name) $conversion
set rctype($name) $ctype
return
}
proc ::critcl::cproc {name adefs rtype {body "#"} args} {
SkipIgnored [set file [This]]
AbortWhenCalledAfterBuild
set acname 0
set passcd 0
set aoffset 0
while {[string match "-*" $args]} {
switch -- [set opt [lindex $args 0]] {
-cname { set acname [lindex $args 1] }
-pass-cdata { set passcd [lindex $args 1] }
-arg-offset { set aoffset [lindex $args 1] }
default {
error "Unknown option $opt, expected one of -cname, or -pass-cdata"
}
}
set args [lrange $args 2 end]
}
switch -regexp -- [join [argoptional $adefs] {}] {
^0*$ -
^0*1+0*$ {
# no optional arguments, or a single optional block at the
# beginning, middle, or end of the argument list is what
# we are able to handle.
}
default {
error "Unable to handle multiple segments of optional arguments"
}
}
if {$acname} {
BeginCommand static $name $adefs $rtype $body
set ns {}
set cns {}
set wname $name
set cname c_$name
} else {
lassign [BeginCommand public $name $adefs $rtype $body] ns cns name cname
set wname tcl_$cns$cname
set cname c_$cns$cname
}
set names [argnames $adefs]
set cargs [argcsignature $adefs]
set cnames [argcnames $adefs]
if {$passcd} {
set cargs [linsert $cargs 0 {ClientData clientdata}]
set cnames [linsert $cnames 0 cd]
}
Emit [join [argsupport $adefs] \n]
# Emit either the low-level function, or, if it wasn't defined
# here, a reference to the shim we can use.
if {$body ne "#"} {
Emit "static [ResultCType $rtype] "
Emitln "${cname}([join $cargs {, }])"
Emit \{\n
lassign [HeaderLines $body] leadoffset body
if {$v::options(lines)} {
Emit [at::CPragma $leadoffset -2 $file]
}
Emit $body
Emitln \n\}
} else {
Emitln "#define $cname $name"
}
# Construct the shim handling the conversion between Tcl and C
# realms.
EmitShimHeader $wname
EmitShimVariables $adefs $rtype
EmitWrongArgsCheck $adefs $aoffset
EmitArgumentConversion $adefs $aoffset
EmitCall $cname $cnames $rtype
EmitShimFooter $rtype
EndCommand
return
}
proc ::critcl::cinit {text edecls} {
set file [SkipIgnored [set file [This]]]
AbortWhenCalledAfterBuild
set digesta [UUID.extend $file .cinit.f $text]
set digestb [UUID.extend $file .cinit.e $edecls]
set initc {}
set skip [Lines $text]
lassign [HeaderLines $text] leadoffset text
if {$v::options(lines)} {
append initc [at::CPragma $leadoffset -2 $file]
}
append initc $text \n
set edec {}
lassign [HeaderLines $edecls] leadoffset edecls
if {$v::options(lines)} {
incr leadoffset $skip
append edec [at::CPragma $leadoffset -2 $file]
}
append edec $edecls \n
dict update v::code($file) config c {
dict append c initc $initc \n
dict append c edecls $edec \n
}
return
}
# # ## ### ##### ######## ############# #####################
## Public API to code origin handling.
namespace eval ::critcl::at {
namespace export caller caller! here here! get get* incr incrt =
catch { namespace ensemble create }
}
# caller - stash caller location, possibly modified (level change, line offset)
# caller! - format & return caller location, clears stash
# here - stash current location
# here! - return format & return current location, clears stash
# incr* - modify stashed location (only line number, not file).
# get - format, return, and clear stash
# get* - format & return stash
proc ::critcl::at::caller {{off 0} {level 0}} {
::incr level -3
Where $off $level [::critcl::This]
return
}
proc ::critcl::at::caller! {{off 0} {level 0}} {
::incr level -3
Where $off $level [::critcl::This]
return [get]
}
proc ::critcl::at::here {} {
Where 0 -2 [::critcl::This]
return
}
proc ::critcl::at::here! {} {
Where 0 -2 [::critcl::This]
return [get]
}
proc ::critcl::at::get {} {
variable where
if {!$::critcl::v::options(lines)} {
return {}
}
if {![info exists where]} {
return -code error "No location defined"
}
set result [Format $where]
unset where
return $result
}
proc ::critcl::at::get* {} {
variable where
if {![info exists where]} {
return -code error "No location defined"
}
return [Format $where]
}
proc ::critcl::at::= {file line} {
variable where
set where [list $file $line]
return
}
proc ::critcl::at::incr {args} {
variable where
lassign $where file line
foreach offset $args {
::incr line $offset
}
set where [list $file $line]
return
}
proc ::critcl::at::incrt {args} {
variable where
if {$where eq {}} {
return -code error "No location to change"
}
lassign $where file line
foreach text $args {
::incr line [::critcl::Lines $text]
}
set where [list $file $line]
return
}
# # ## ### ##### ######## ############# #####################
## Implementation -- API: Input and Output control
proc ::critcl::collect {script {slot {}}} {
collect_begin $slot
uplevel 1 $script
return [collect_end]
}
proc ::critcl::collect_begin {{slot {}}} {
# Divert the collection of code fragments to slot
# (output control). Stack on any previous diversion.
variable v::this
# See critcl::This for where this information is injected into the
# code generation system.
if {$slot eq {}} {
set slot MEMORY[expr { [info exists this]
? [llength $this]
: 0 }]
}
# Prefix prevents collision of slot names and file paths.
lappend this critcl://$slot
return
}
proc ::critcl::collect_end {} {
# Stop last diversion, and return the collected information as
# single string of C code.
variable v::this
# See critcl::This for where this information is injected into the
# code generation system.
# Ensure that a diversion is actually open.
if {![info exists this] || ![llength $this]} {
return -code error "collect_end mismatch, no diversions active"
}
set slot [Dpop]
set block {}
foreach digest [dict get $v::code($slot) config fragments] {
append block "[Separator]\n\n"
append block [dict get $v::code($slot) config block $digest]\n
}
# Drop all the collected data. Note how anything other than the C
# code fragments is lost, and how cbuild results are removed
# also. These do not belong anyway.
unset v::code($slot)
return $block
}
proc ::critcl::Dpop {} {
variable v::this
# Get current slot, and pop from the diversion stack.
# Remove stack when it becomes empty.
set slot [lindex $this end]
set v::this [lrange $this 0 end-1]
if {![llength $this]} {
unset this
}
return $slot
}
proc ::critcl::source {path} {
# Source a critcl file in the context of the current file,
# i.e. [This]. Enables the factorization of a large critcl
# file into smaller, easier to read pieces.
SkipIgnored [set file [This]]
AbortWhenCalledAfterBuild
msg -nonewline " (importing $path)"
set undivert 0
variable v::this
if {![info exists this] || ![llength $this]} {
# critcl::source is recording the critcl commands in the
# context of the toplevel file which started the chain the
# critcl::source. So why are we twiddling with the diversion
# state?
#
# The condition above tells us that we are in the first
# non-diverted critcl::source called by the context. [This]
# returns that context. Due to our use of regular 'source' (*)
# during its execution [This] would return the sourced file as
# context. Wrong. Our fix for this is to perform, essentially,
# an anti-diversion. Saving [This] as diversion, forces it to
# return the proper value during the whole sourcing.
#
# And if the critcl::source is run in an already diverted
# context then the changes to [info script] by 'source' do not
# matter, making an anti-diversion unnecessary.
#
# Diversions inside of 'source' will work as usual, given
# their nesting nature.
#
# (Ad *) And we use 'source' as only this ensures proper
# collection of [info frame] location information.
lappend this [This]
set undivert 1
}
foreach f [Expand $file $path] {
set v::source $f
# The source file information is used by critcl::at::Where
#uplevel 1 [Cat $f]
uplevel #0 [list ::source $f]
unset -nocomplain v::source
}
if {$undivert} Dpop
return
}
# # ## ### ##### ######## ############# #####################
## Implementation -- API: Control & Interface
proc ::critcl::owns {args} {}
proc ::critcl::cheaders {args} {
SkipIgnored [This]
AbortWhenCalledAfterBuild
return [SetParam cheaders $args]
}
proc ::critcl::csources {args} {
SkipIgnored [This]
AbortWhenCalledAfterBuild
return [SetParam csources $args 1 1]
}
proc ::critcl::clibraries {args} {
SkipIgnored [This]
AbortWhenCalledAfterBuild
return [SetParam clibraries $args]
}
proc ::critcl::cobjects {args} {
SkipIgnored [This]
AbortWhenCalledAfterBuild
return [SetParam cobjects $args]
}
proc ::critcl::tsources {args} {
set file [SkipIgnored [This]]
AbortWhenCalledAfterBuild
# This, 'license', 'meta?' and 'meta' are the only places where we
# are not extending the UUID. Because the companion Tcl sources
# (count, order, and content) have no bearing on the binary at
# all.
InitializeFile $file
dict update v::code($file) config c {
foreach f $args {
foreach e [Expand $file $f] {
dict lappend c tsources $e
ScanDependencies $file $e
}
}
}
return
}
proc ::critcl::cflags {args} {
set file [SkipIgnored [This]]
AbortWhenCalledAfterBuild
if {![llength $args]} return
UUID.extend $file .cflags $args
dict update v::code($file) config c {
foreach flag $args {
dict lappend c cflags $flag
}
}
return
}
proc ::critcl::ldflags {args} {
set file [SkipIgnored [This]]
AbortWhenCalledAfterBuild
if {![llength $args]} return
UUID.extend $file .ldflags $args
dict update v::code($file) config c {
foreach flag $args {
# Drop any -Wl prefix which will be added back a moment
# later, otherwise it would be doubled, breaking the command.
regsub -all {^-Wl,} $flag {} flag
dict lappend c ldflags -Wl,$flag
}
}
return
}
proc ::critcl::framework {args} {
SkipIgnored [This]
AbortWhenCalledAfterBuild
# Check if we are building for OSX and ignore the command if we
# are not. Our usage of "actualtarget" means that we allow for a
# cross-compilation environment to OS X as well.
if {![string match "macosx*" [actualtarget]]} return
foreach arg $args {
# if an arg contains a slash it must be a framework path
if {[string first / $arg] == -1} {
ldflags -framework $arg
} else {
cflags -F$arg
ldflags -F$arg
}
}
return
}
proc ::critcl::tcl {version} {
set file [SkipIgnored [This]]
AbortWhenCalledAfterBuild
UUID.extend $file .mintcl $version
dict set v::code($file) config mintcl $version
# This is also a dependency to record in the meta data. A 'package
# require' is not needed. This can be inside of the generated and
# loaded C code.
ImetaAdd $file require [list [list Tcl $version]]
return
}
proc ::critcl::tk {} {
set file [SkipIgnored [This]]
AbortWhenCalledAfterBuild
UUID.extend $file .tk 1
dict set v::code($file) config tk 1
# This is also a dependency to record in the meta data. A 'package
# require' is not needed. This can be inside of the generated and
# loaded C code.
ImetaAdd $file require Tk
return
}
# Register a shared library for pre-loading - this will eventually be
# redundant when TIP #239 is widely available
proc ::critcl::preload {args} {
set file [SkipIgnored [This]]
AbortWhenCalledAfterBuild
if {![llength $args]} return
UUID.extend $file .preload $args
dict update v::code($file) config c {
foreach lib $args {
dict lappend c preload $lib
}
}
return
}
proc ::critcl::license {who args} {
set file [SkipIgnored [This]]
AbortWhenCalledAfterBuild
set who [string trim $who]
if {$who ne ""} {
set license "This software is copyrighted by $who.\n"
} else {
set license ""
}
set elicense [LicenseText $args]
append license $elicense
# This, 'tsources', 'meta?', and 'meta' are the only places where
# we are not extending the UUID. Because the license text has no
# bearing on the binary at all.
InitializeFile $file
ImetaSet $file license [Text2Words $elicense]
ImetaSet $file author [Text2Authors $who]
return
}
proc ::critcl::LicenseText {words} {
if {[llength $words]} {
# Use the supplied license details as our suffix.
return [join $words]
} else {
# No details were supplied, fall back to the critcl license as
# template for the generated package. This is found in a
# sibling of this file.
# We strip the first 2 lines from the file, this gets rid of
# the author information for critcl itself, allowing us to
# replace it by the user-supplied author.
variable mydir
set f [file join $mydir license.terms]
return [join [lrange [split [Cat $f] \n] 2 end] \n]
}
}
# # ## ### ##### ######## ############# #####################
## Implementation -- API: meta data (teapot)
proc ::critcl::description {text} {
set file [SkipIgnored [This]]
AbortWhenCalledAfterBuild
InitializeFile $file
ImetaSet $file description [Text2Words $text]
return
}
proc ::critcl::summary {text} {
set file [SkipIgnored [This]]
AbortWhenCalledAfterBuild
InitializeFile $file
ImetaSet $file summary [Text2Words $text]
return
}
proc ::critcl::subject {args} {
set file [SkipIgnored [This]]
AbortWhenCalledAfterBuild
InitializeFile $file
ImetaAdd $file subject $args
return
}
proc ::critcl::meta {key args} {
set file [SkipIgnored [This]]
AbortWhenCalledAfterBuild
# This, 'meta?', 'license', and 'tsources' are the only places
# where we are not extending the UUID. Because the meta data has
# no bearing on the binary at all.
InitializeFile $file
dict update v::code($file) config c {
dict update c meta m {
foreach v $args { dict lappend m $key $v }
}
}
return
}
proc ::critcl::meta? {key} {
set file [SkipIgnored [This]]
AbortWhenCalledAfterBuild
# This, 'meta', 'license', and 'tsources' are the only places
# where we are not extending the UUID. Because the meta data has
# no bearing on the binary at all.
InitializeFile $file
if {[dict exists $v::code($file) config package $key]} {
return [dict get $v::code($file) config package $key]
}
if {[dict exists $v::code($file) config meta $key]} {
return [dict get $v::code($file) config meta $key]
}
return -code error "Unknown meta data key \"$key\""
}
proc ::critcl::ImetaSet {file key words} {
dict set v::code($file) config package $key $words
#puts |||$key|%|[dict get $v::code($file) config package $key]|
return
}
proc ::critcl::ImetaAdd {file key words} {
dict update v::code($file) config c {
dict update c package p {
foreach word $words {
dict lappend p $key $word
}
}
}
#puts |||$key|+|[dict get $v::code($file) config package $key]|
return
}
proc ::critcl::Text2Words {text} {
regsub -all {[ \t\n]+} $text { } text
return [split [string trim $text]]
}
proc ::critcl::Text2Authors {text} {
regsub -all {[ \t\n]+} $text { } text
set authors {}
foreach a [split [string trim $text] ,] {
lappend authors [string trim $a]
}
return $authors
}
proc ::critcl::GetMeta {file} {
if {![dict exists $v::code($file) config meta]} {
set result {}
} else {
set result [dict get $v::code($file) config meta]
}
# Merge the package information (= system meta data) with the
# user's meta data. The system information overrides anything the
# user may have declared for the reserved keys (name, version,
# platform, as::author, as::build::date, license, description,
# summary, require). Note that for the internal bracketing code
# the system information may not exist, hence the catch. Might be
# better to indicate the bracket somehow and make it properly
# conditional.
#puts %$file
catch {
set result [dict merge $result [dict get $v::code($file) config package]]
}
return $result
}
# # ## ### ##### ######## ############# #####################
## Implementation -- API: user configuration options.
proc ::critcl::userconfig {cmd args} {
set file [SkipIgnored [This]]
AbortWhenCalledAfterBuild
InitializeFile $file
if {![llength [info commands ::critcl::UC$cmd]]} {
return -code error "Unknown method \"$cmd\""
}
# Dispatch
return [eval [linsert $args 0 ::critcl::UC$cmd $file]]
}
proc ::critcl::UCdefine {file oname odesc otype {odefault {}}} {
# When declared without a default determine one of our own. Bool
# flag default to true, whereas enum flags, which is the rest,
# default to their first value.
# The actual definition ignores the config description. This
# argument is only used by the static code scanner supporting
# TEA. See ::critcl::scan::userconfig.
if {[llength [info level 0]] < 6} {
set odefault [UcDefault $otype]
}
# Validate the default against the type too, before saving
# everything.
UcValidate $oname $otype $odefault
UUID.extend $file .uc-def [list $oname $otype $odefault]
dict set v::code($file) config userflag $oname type $otype
dict set v::code($file) config userflag $oname default $odefault
return
}
proc ::critcl::UCset {file oname value} {
# NOTE: We can set any user flag we choose, even if not declared
# yet. Validation of the value happens on query, at which time the
# flag must be declared.
dict set v::code($file) config userflag $oname value $value
return
}
proc ::critcl::UCquery {file oname} {
# Prefer cached data. This is known as declared, defaults merged,
# validated.
if {[dict exists $v::code($file) config userflag $oname =]} {
return [dict get $v::code($file) config userflag $oname =]
}
# Reject use of undeclared user flags.
if {![dict exists $v::code($file) config userflag $oname type]} {
error "Unknown user flag \"$oname\""
}
# Check if a value was supplied by the calling app. If not, fall
# back to the declared default.
if {[dict exists $v::code($file) config userflag $oname value]} {
set value [dict get $v::code($file) config userflag $oname value]
} else {
set value [dict get $v::code($file) config userflag $oname default]
}
# Validate value against the flag's type.
set otype [dict get $v::code($file) config userflag $oname type]
UcValidate $oname $otype $value
# Fill cache
dict set v::code($file) config userflag $oname = $value
return $value
}
proc ::critcl::UcValidate {oname otype value} {
switch -exact -- $otype {
bool {
if {![string is bool -strict $value]} {
error "Expected boolean for user flag \"$oname\", got \"$value\""
}
}
default {
if {[lsearch -exact $otype $value] < 0} {
error "Expected one of [linsert [join $otype {, }] end-1 or] for user flag \"$oname\", got \"$value\""
}
}
}
}
proc ::critcl::UcDefault {otype} {
switch -exact -- $otype {
bool {
return 1
}
default {
return [lindex $otype 0]
}
}
}
# # ## ### ##### ######## ############# #####################
## Implementation -- API: API (stubs) management
proc ::critcl::api {cmd args} {
set file [SkipIgnored [This]]
AbortWhenCalledAfterBuild
if {![llength [info commands ::critcl::API$cmd]]} {
return -code error "Unknown method \"$cmd\""
}
# Dispatch
return [eval [linsert $args 0 ::critcl::API$cmd $file]]
}
proc ::critcl::APIscspec {file scspec} {
UUID.extend $file .api-scspec $scspec
dict set v::code($file) config api_scspec $scspec
return
}
proc ::critcl::APIimport {file name version} {
# First we request the imported package, giving it a chance to
# generate the headers searched for in a moment (maybe it was
# critcl based as well, and generates things dynamically).
# Note that this can fail, for example in a cross-compilation
# environment. Such a failure however does not imply that the
# required API headers are not present, so we can continue.
catch {
package require $name $version
}
ImetaAdd $file require [list [list $name $version]]
# Now we check that the relevant headers of the imported package
# can be found in the specified search paths.
set cname [string map {:: _} $name]
set at [API_locate $cname]
if {$at eq {}} {
error "Headers for API $name not found"
} else {
msg -nonewline " (stubs import $name $version @ $at/$cname)"
}
set def [list $name $version]
UUID.extend $file .api-import $def
dict update v::code($file) config c {
dict lappend c api_use $def
}
# At last look for the optional .decls file. Ignore if there is
# none. Decode and return contained stubs table otherwise.
set decls $at/$cname/$cname.decls
if {[file exists $decls]} {
package require stubs::reader
set T [stubs::container::new]
stubs::reader::file T $decls
return $T
}
return
}
proc ::critcl::APIexport {file name} {
msg -nonewline " (stubs export $name)"
UUID.extend $file .api-self $name
return [dict set v::code($file) config api_self $name]
}
proc ::critcl::APIheader {file args} {
UUID.extend $file .api-headers $args
return [SetParam api_hdrs $args]
}
proc ::critcl::APIextheader {file args} {
UUID.extend $file .api-eheaders $args
return [SetParam api_ehdrs $args 0]
}
proc ::critcl::APIfunction {file rtype name arguments} {
package require stubs::reader
# Generate a declaration as it would have come straight out of the
# stubs reader. To this end we generate a C code fragment as it
# would be have been written inside of a .decls file.
# TODO: We should record this as well, and later generate a .decls
# file as part of the export. Or regenerate it from the internal
# representation.
if {[llength $arguments]} {
foreach {t a} $arguments {
lappend ax "$t $a"
}
} else {
set ax void
}
set decl [stubs::reader::ParseDecl "$rtype $name ([join $ax ,])"]
UUID.extend $file .api-fun $decl
dict update v::code($file) config c {
dict lappend c api_fun $decl
}
return
}
proc ::critcl::API_locate {name} {
foreach dir [SystemIncludePaths [This]] {
if {[API_at $dir $name]} { return $dir }
}
return {}
}
proc ::critcl::API_at {dir name} {
foreach suffix {
Decls.h StubLib.h
} {
if {![file exists [file join $dir $name $name$suffix]]} { return 0 }
}
return 1
}
proc ::critcl::API_setup {file} {
package require stubs::gen
lassign [API_setup_import $file] iprefix idefines
dict set v::code($file) result apidefines $idefines
append prefix $iprefix
append prefix [API_setup_export $file]
# Save prefix to result dictionary for pickup by Compile.
if {$prefix eq ""} return
dict set v::code($file) result apiprefix $prefix\n
return
}
proc ::critcl::API_setup_import {file} {
if {![dict exists $v::code($file) config api_use]} {
return ""
}
#msg -nonewline " (stubs import)"
set prefix ""
set defines {}
foreach def [dict get $v::code($file) config api_use] {
lassign $def iname iversion
set cname [string map {:: _} $iname]
set upname [string toupper $cname]
set capname [stubs::gen::cap $cname]
set import [critcl::at::here!][subst -nocommands {
/* Import API: $iname */
#define USE_${upname}_STUBS 1
#include <$cname/${cname}Decls.h>
}]
append prefix \n$import
ccode $import
# TODO :: DOCUMENT environment of the cinit code.
cinit [subst -nocommands {
if (!${capname}_InitStubs (ip, "$iversion", 0)) {
return TCL_ERROR;
}
}] [subst -nocommands {
#include <$cname/${cname}StubLib.h>
}]
lappend defines -DUSE_${upname}_STUBS=1
}
return [list $prefix $defines]
}
proc ::critcl::API_setup_export {file} {
if {![dict exists $v::code($file) config api_hdrs] &&
![dict exists $v::code($file) config api_ehdrs] &&
![dict exists $v::code($file) config api_fun]} return
if {[dict exists $v::code($file) config api_self]} {
# API name was declared explicitly
set ename [dict get $v::code($file) config api_self]
} else {
# API name is implicitly defined, is package name.
set ename [dict get $v::code($file) config package name]
}
set prefix ""
#msg -nonewline " (stubs export)"
set cname [string map {:: _} $ename]
set upname [string toupper $cname]
set capname [stubs::gen::cap $cname]
set import [at::here!][subst -nocommands {
/* Import our own exported API: $ename, mapping disabled */
#undef USE_${upname}_STUBS
#include <$cname/${cname}Decls.h>
}]
append prefix \n$import
ccode $import
# Generate the necessary header files.
append sdecls "\#ifndef ${cname}_DECLS_H\n"
append sdecls "\#define ${cname}_DECLS_H\n"
append sdecls "\n"
append sdecls "\#include <tcl.h>\n"
if {[dict exists $v::code($file) config api_ehdrs]} {
append sdecls "\n"
file mkdir $v::cache/$cname
foreach hdr [dict get $v::code($file) config api_ehdrs] {
append sdecls "\#include \"[file tail $hdr]\"\n"
}
}
if {[dict exists $v::code($file) config api_hdrs]} {
append sdecls "\n"
file mkdir $v::cache/$cname
foreach hdr [dict get $v::code($file) config api_hdrs] {
Copy $hdr $v::cache/$cname
append sdecls "\#include \"[file tail $hdr]\"\n"
}
}
# Insert code to handle the storage class settings on Windows.
append sdecls [string map \
[list @cname@ $cname @up@ $upname] \
$v::storageclass]
package require stubs::container
package require stubs::reader
package require stubs::gen
package require stubs::gen::header
package require stubs::gen::init
package require stubs::gen::lib
package require stubs::writer
# Implied .decls file. Not actually written, only implied in the
# stubs container invocations, as if read from such a file.
set T [stubs::container::new]
stubs::container::library T $ename
stubs::container::interface T $cname
if {[dict exists $v::code($file) config api_scspec]} {
stubs::container::scspec T \
[dict get $v::code($file) config api_scspec]
}
if {[dict exists $v::code($file) config api_fun]} {
set index 0
foreach decl [dict get $v::code($file) config api_fun] {
#puts D==|$decl|
stubs::container::declare T $cname $index generic $decl
incr index
}
append sdecls "\n"
append sdecls [stubs::gen::header::gen $T $cname]
}
append sdecls "\#endif /* ${cname}_DECLS_H */\n"
set comment "/* Stubs API Export: $ename */"
set thedecls [stubs::writer::gen $T]
set slib [stubs::gen::lib::gen $T]
set sinitstatic " $comment\n "
append sinitstatic [stubs::gen::init::gen $T]
set pn [dict get $v::code($file) config package name]
set pv [dict get $v::code($file) config package version]
set sinitrun $comment\n
append sinitrun "Tcl_PkgProvideEx (ip, \"$pn\", \"$pv\", (ClientData) &${cname}Stubs);"
# Save the header files to the result cache for pickup (importers
# in mode "compile & run", or by the higher-level code doing a
# "generate package")
WriteCache $cname/${cname}Decls.h $sdecls
WriteCache $cname/${cname}StubLib.h $slib
WriteCache $cname/${cname}.decls $thedecls
dict update v::code($file) result r {
dict lappend r apiheader [file join $v::cache $cname]
}
cinit $sinitrun $sinitstatic
cflags -DBUILD_$cname
return $prefix
}
# # ## ### ##### ######## ############# #####################
## Implementation -- API: Introspection
proc ::critcl::check {args} {
set file [SkipIgnored [This] 0]
AbortWhenCalledAfterBuild
switch -exact -- [llength $args] {
1 {
set label Checking
set code [lindex $args 0]
}
2 {
lassign $args label code
}
default {
return -code error "wrong#args: Expected ?label? code"
}
}
set src [WriteCache check_[pid].c $code]
set obj [file rootname $src][getconfigvalue object]
# See also the internal helper 'Compile'. Thre code here is in
# essence a simplified form of that.
set cmdline [getconfigvalue compile]
lappendlist cmdline [GetParam $file cflags]
lappendlist cmdline [SystemIncludes $file]
lappendlist cmdline [CompileResult $obj]
lappend cmdline $src
LogOpen $file
Log* "${label}... "
StatusReset
set ok [ExecWithLogging $cmdline OK FAILED]
StatusReset
LogClose
clean_cache check_[pid].*
return $ok
}
proc ::critcl::checklink {args} {
set file [SkipIgnored [This] 0]
AbortWhenCalledAfterBuild
switch -exact -- [llength $args] {
1 {
set label Checking
set code [lindex $args 0]
}
2 {
lassign $args label code
}
default {
return -code error "wrong#args: Expected ?label? code"
}
}
set src [WriteCache check_[pid].c $code]
set obj [file rootname $src][getconfigvalue object]
# See also the internal helper 'Compile'. Thre code here is in
# essence a simplified form of that.
set cmdline [getconfigvalue compile]
lappendlist cmdline [GetParam $file cflags]
lappendlist cmdline [SystemIncludes $file]
lappendlist cmdline [CompileResult $obj]
lappend cmdline $src
LogOpen $file
Log* "${label} (build)... "
StatusReset
set ok [ExecWithLogging $cmdline OK FAILED]
StatusReset
if {!$ok} {
LogClose
clean_cache check_[pid].*
return 0
}
set out [file join $v::cache a_[pid].out]
set cmdline [getconfigvalue link]
if {$option::debug_symbols} {
lappendlist cmdline [getconfigvalue link_debug]
} else {
lappendlist cmdline [getconfigvalue strip]
lappendlist cmdline [getconfigvalue link_release]
}
lappendlist cmdline [LinkResult $out]
lappendlist cmdline $obj
lappendlist cmdline [SystemLibraries]
lappendlist cmdline [FixLibraries [GetParam $file clibraries]]
lappendlist cmdline [GetParam $file ldflags]
Log* "${label} (link)... "
StatusReset
set ok [ExecWithLogging $cmdline OK ERR]
LogClose
clean_cache check_[pid].* a_[pid].*
return $ok
}
proc ::critcl::compiled {} {
SkipIgnored [This] 1
AbortWhenCalledAfterBuild
return 0
}
proc ::critcl::compiling {} {
SkipIgnored [This] 0
AbortWhenCalledAfterBuild
# Check that we can indeed run a compiler
# Should only need to do this if we have to compile the code?
if {[auto_execok [lindex [getconfigvalue compile] 0]] eq ""} {
set v::compiling 0
} else {
set v::compiling 1
}
return $v::compiling
}
proc ::critcl::done {} {
set file [SkipIgnored [This] 1]
return [expr {[info exists v::code($file)] &&
[dict exists $v::code($file) result closed]}]
}
proc ::critcl::failed {} {
SkipIgnored [This] 0
if {$v::buildforpackage} { return 0 }
return [cbuild [This] 0]
}
proc ::critcl::load {} {
SkipIgnored [This] 1
if {$v::buildforpackage} { return 1 }
return [expr {![cbuild [This]]}]
}
# # ## ### ##### ######## ############# #####################
## Default error behaviour
proc ::critcl::error {msg} {
return -code error $msg
}
# # ## ### ##### ######## ############# #####################
## Default message behaviour
proc ::critcl::msg {args} {
# ignore message (compile & run)
}
# # ## ### ##### ######## ############# #####################
## Default print behaviour
proc ::critcl::print {args} {
# API same as for builtin ::puts. Use as is.
return [eval [linsert $args 0 ::puts]]
}
# # ## ### ##### ######## ############# #####################
## Runtime support to handle the possibility of a prebuilt package using
## the .tcl file with embedded C as its own companon defining regular
## Tcl code for the package as well. If the critcl package is loaded
## already this will cause it to ignore the C definitions, with best
## guesses for failed, done, load, check, compiled, and compiling.
proc ::critcl::Ignore {f} {
set v::ignore([file normalize $f]) .
return
}
proc ::critcl::SkipIgnored {f {result {}}} {
if {[info exists v::ignore($f)]} { return -code return $result }
return $f
}
# # ## ### ##### ######## ############# #####################
## Implementation -- API: Build Management
proc ::critcl::config {option args} {
if {![info exists v::options($option)] || [llength $args] > 1} {
error "option must be one of: [lsort [array names v::options]]"
}
if {![llength $args]} {
return $v::options($option)
}
set v::options($option) [lindex $args 0]
}
proc ::critcl::debug {args} {
# Replace 'all' everywhere, and squash duplicates, whether from
# this, or user-specified.
set args [string map {all {memory symbols}} $args]
set args [lsort -unique $args]
foreach arg $args {
switch -- $arg {
memory { foreach x [getconfigvalue debug_memory] { cflags $x } }
symbols { foreach x [getconfigvalue debug_symbols] { cflags $x }
set option::debug_symbols 1
}
default {
error "unknown critcl::debug option - $arg"
}
}
}
return
}
# # ## ### ##### ######## ############# #####################
## Implementation -- API: Result Cache
proc ::critcl::cache {{dir ""}} {
if {[llength [info level 0]] == 2} {
set v::cache [file normalize $dir]
}
return $v::cache
}
proc ::critcl::clean_cache {args} {
if {![llength $args]} { lappend args * }
foreach pattern $args {
foreach file [glob -nocomplain -directory $v::cache $pattern] {
file delete -force $file
}
}
return
}
# # ## ### ##### ######## ############# #####################
## Implementation -- API: Build Configuration
# read toolchain information from config file
proc ::critcl::readconfig {config} {
variable run
variable configfile $config
set cfg [open $config]
set knowntargets [list]
set cont ""
set whenplat ""
interp eval $run set platform $v::buildplatform
set i 0
while {[gets $cfg line] >= 0} {
incr i
if {[set line [string trim $line]] ne ""} {
# config lines can be continued using trailing backslash
if {[string index $line end] eq "\\"} {
append cont " [string range $line 0 end-1]"
continue
}
if {$cont ne ""} {
append cont $line
set line [string trim $cont]
set cont ""
}
# At this point we have a complete line/command in 'line'.
# We expect the following forms of input:
#
# (1.) if {...} {.............} - Tcl command, run in the
# backend interpreter.
# Note that this can EXIT
# the application using
# the critcl package.
# (2.) set VAR VALUE.......... - Ditto.
# (3.) # ..................... - Comment. Skipped
# (4.) PLATFORM VAR VALUE...... - Platform-specific
# configuration variable
# and value.
# (4a) PLATFORM when ......... - Makes the PLATFORM
# conditional on the
# expression after the
# 'when' keyword. This
# uses variables set by
# (1) and/or (2). The
# expression is run in the
# backend interpreter. If
# and only if PLATFORM is
# a prefix of the current
# build platform, or the
# reverse, then the code
# with an TRUE when is
# chosen as the
# configuration.
# (4b) PLATFORM target ?actual? - Marks the platform as a
# cross-compile target,
# and actual is the
# platform identifier of
# the result. If not
# specified it defaults to
# PLATFORM.
# (4c) PLATFORM copy PARENT... - Copies the currently defined
# configuration variables and
# values to the settings for
# this platform.
# (5.) VAR VALUE............... - Default configuration
# variable, and value.
set plat [lindex [split $line] 0]
# (1), or (2)
if {$plat eq "set" || $plat eq "if"} {
while {![info complete $line] && ![eof $cfg]} {
if {[gets $cfg more] == -1} {
set msg "incomplete command in Critcl Config file "
append msg "starting at line $i"
error $msg
}
append line "\n$more"
}
interp eval $run $line
continue
}
# (3)
if {$plat eq "#"} continue
# (4), or (5).
if {[lsearch -exact $v::configvars $plat] != -1} {
# (5) default config option
set cmd ""
if {![regexp {(\S+)\s+(.*)} $line -> type cmd]} {
# cmd is empty
set type $plat
set cmd ""
}
set plat ""
} else {
# (4) platform config option
if {![regexp {(\S+)\s+(\S+)\s+(.*)} $line -> p type cmd]} {
# cmd is empty
set type [lindex $line 1]
set cmd ""
}
# (4a) if and only if either build platform or config
# code are a prefix of each other can the 'when'
# condition be evaluated and override the
# standard selection for the configuration.
if {$type eq "when" &&
( [string match ${v::buildplatform}* $plat] ||
[string match ${plat}* $v::buildplatform] )} {
set res ""
catch {
set res [interp eval $run expr $cmd]
}
switch $res {
"" -
0 { set whenfalse($plat) 1 }
1 { set whenplat $plat }
}
}
lappend knowntargets $plat
}
switch -exact -- $type {
target {
# (4b) cross compile target.
# cmd = actual target platform identifier.
if {$cmd eq ""} {
set cmd $plat
}
set v::xtargets($plat) $cmd
}
copy {
# (4c) copy an existing config
# XXX - should we error out if no definitions exist
# for parent platform config
# $cmd contains the parent platform
foreach {key val} [array get v::toolchain "$cmd,*"] {
set key [lindex [split $key ,] 1]
set v::toolchain($plat,$key) $val
}
}
default {
set v::toolchain($plat,$type) $cmd
}
}
}
}
set knowntargets [lsort -unique $knowntargets]
close $cfg
# Config file processing has completed.
# Now select the platform to configure the
# compiler backend with.
set v::knowntargets $knowntargets
# The config file may have selected a configuration based on the
# TRUE when conditions. Which were matched to v::buildplatform,
# making the chosen config a variant of it. If that did not happen
# a platform is chosen from the set of defined targets.
if {$whenplat ne ""} {
set match [list $whenplat]
} else {
set match [critcl::chooseconfig $v::buildplatform]
}
# Configure the backend.
setconfig "" ;# defaults
if {[llength $match]} {
setconfig [lindex $match 0]
} else {
setconfig $v::buildplatform
}
return
}
proc ::critcl::chooseconfig {targetconfig {err 0}} {
# first try to match exactly
set match [lsearch -exact -all -inline $v::knowntargets $targetconfig]
# on failure, try to match as glob pattern
if {![llength $match]} {
set match [lsearch -glob -all -inline $v::knowntargets $targetconfig]
}
# on failure, error out if requested
if {![llength $match] && $err} {
error "unknown target $targetconfig - use one of $v::knowntargets"
}
return $match
}
proc ::critcl::showconfig {{fd ""}} {
variable run
variable configfile
# XXX replace gen - v::buildplatform
# XXX Do not use v::targetplatform here. Use v::config.
# XXX Similarly in setconfig.
set gen $v::buildplatform
if {$v::targetplatform eq ""} {
set plat "default"
} else {
set plat $v::targetplatform
}
set out [list]
if {$plat eq $gen} {
lappend out "Config: $plat"
} else {
lappend out "Config: $plat (built on $gen)"
}
lappend out "Origin: $configfile"
lappend out " [format %-15s cache] [critcl::cache]"
foreach var [lsort $v::configvars] {
set val [getconfigvalue $var]
set line " [format %-15s $var]"
foreach word [split [string trim $val]] {
if {[set word [string trim $word]] eq ""} continue
if {[string length "$line $word"] > 70} {
lappend out "$line \\"
set line " [format %-15s { }] $word"
} else {
set line "$line $word"
}
}
lappend out $line
}
# Tcl variables
set vars [list]
set max 0
foreach idx [array names v::toolchain $v::targetplatform,*] {
set var [lindex [split $idx ,] 1]
if {[set len [string length $var]] > $max} {
set max $len
}
if {$var ne "when" && ![info exists c::$var]} {
lappend vars $idx $var
}
}
if {[llength $vars]} {
lappend out "Tcl variables:"
foreach {idx var} $vars {
set val $v::toolchain($idx)
if {[llength $val] == 1} {
# for when someone inevitably puts quotes around
# values - e.g. "Windows NT"
set val [lindex $val 0]
}
lappend out " [format %-${max}s $var] $val"
}
}
set out [join $out \n]
if {$fd ne ""} {
puts $fd $out
} else {
return $out
}
}
proc ::critcl::showallconfig {{ofd ""}} {
variable configfile
set txt [Cat $configfile]
if {$ofd ne ""} {
puts $ofd $txt
} else {
return $txt
}
}
proc ::critcl::setconfig {targetconfig} {
set v::targetconfig $targetconfig
# Strip the compiler information from the configuration to get the
# platform identifier embedded into it. This is a semi-recurrence
# of the original hardwired block handling win32/gcc/cl. We can
# partly emulate this with 'platform' directives in the Config
# file, however this breaks down when trying to handle the default
# settings. I.e. something like FOO-gcc which has no configuration
# block in the file uses the defaults, and thus has no proper
# place for a custom platform directive. So we have to do it here,
# in code. For symmetry the other compilers (-cc, -cl) are handled
# as well.
set v::targetplatform $targetconfig
foreach p {gcc cc_r xlc xlc_r cc cl} {
if {[regsub -- "-$p\$" $v::targetplatform {} v::targetplatform]} break
}
set c::platform ""
set c::sharedlibext ""
foreach var $v::configvars {
if {[info exists v::toolchain($targetconfig,$var)]} {
set c::$var $v::toolchain($targetconfig,$var)
if {$var eq "platform"} {
set px [getconfigvalue platform]
set v::targetplatform [lindex $px 0]
set v::version [lindex $px 1]
}
}
}
if {[info exists ::env(CFLAGS)]} {
variable c::compile
append c::compile " $::env(CFLAGS)"
}
if {[info exists ::env(LDFLAGS)]} {
variable c::link
append c::link " $::env(LDFLAGS)"
append c::link_preload " $::env(LDFLAGS)"
}
if {[string match $v::targetplatform $v::buildplatform]} {
# expand platform to match host if it contains wildcards
set v::targetplatform $v::buildplatform
}
if {$c::platform eq ""} {
# default config platform (mainly for the "show" command)
set c::platform $v::targetplatform
}
if {$c::sharedlibext eq ""} {
set c::sharedlibext [info sharedlibextension]
}
cache [file join ~ .critcl $v::targetplatform]
# set any Tcl variables
foreach idx [array names v::toolchain $v::targetplatform,*] {
set var [lindex [split $idx ,] 1]
if {![info exists c::$var]} {
set val $v::toolchain($idx)
if {[llength $val] == 1} {
# for when someone inevitably puts quotes around
# values - e.g. "Windows NT"
set val [lindex $val 0]
}
set $var $val
}
}
return
}
proc ::critcl::getconfigvalue {var} {
variable run
if {[catch {set val [interp eval $run [list subst [set c::$var]]]}]} {
set val [set c::$var]
}
return $val
}
# # ## ### ##### ######## ############# #####################
## Implementation -- API: Application
# The regular commands used by the application, defined in other
# sections of the package are:
#
# C critcl::cache
# C critcl::ccode
# C critcl::chooseconfig
# C critcl::cinit
# C critcl::clean_cache
# C critcl::clibraries
# C critcl::cobjects
# C critcl::config I, lines, force, keepsrc, combine
# C critcl::debug
# C critcl::error | App overrides our implementation.
# C critcl::getconfigvalue
# C critcl::lappendlist
# C critcl::ldflags
# C critcl::preload
# C critcl::readconfig
# C critcl::setconfig
# C critcl::showallconfig
# C critcl::showconfig
proc ::critcl::crosscheck {} {
variable run
global tcl_platform
if {$tcl_platform(platform) eq "windows"} {
set null NUL:
} else {
set null /dev/null
}
if {![catch {
set cmd [linsert $c::version 0 exec]
lappend cmd 2> $null;#@stdout
set config [interp eval $run $cmd]
} msg]} {
set host ""
set target ""
foreach line $config {
foreach arg [split $line] {
if {[string match "--*" $arg]} {
lassign [split [string trim $arg -] =] cfg val
set $cfg $val
}
}
}
if {$host ne $target && [info exists v::xtargets($target)]} {
setconfig $target
print stderr "Cross compiling using $target"
}
# XXX host != target, but not know as config ?
# XXX Currently ignored.
# XXX Throwing an error better ?
}
return
}
# See (XX) at the end of the file (package state variable setup)
# for explanations of the exact differences between these.
proc ::critcl::knowntargets {} {
return $v::knowntargets
}
proc ::critcl::targetconfig {} {
return $v::targetconfig
}
proc ::critcl::targetplatform {} {
return $v::targetplatform
}
proc ::critcl::buildplatform {} {
return $v::buildplatform
}
proc ::critcl::actualtarget {} {
# Check if the chosen target is a cross-compile target. If yes,
# we return the actual platform identifier of the target. This is
# used to select the proper platform director names in the critcl
# cache, generated packages, when searching for preload libraries,
# etc. Whereas the chosen target provides the proper compile
# configuration which will invoke the proper cross-compiler, etc.
if {[info exists v::xtargets($v::targetplatform)]} {
return $v::xtargets($v::targetplatform)
} else {
return $v::targetplatform
}
}
proc ::critcl::sharedlibext {} {
return [getconfigvalue sharedlibext]
}
proc ::critcl::buildforpackage {{buildforpackage 1}} {
set v::buildforpackage $buildforpackage
return
}
proc ::critcl::cbuild {file {load 1}} {
if {[info exists v::code($file,failed)] && !$load} {
set v::buildforpackage 0
return $v::code($file,failed)
}
StatusReset
# Determine if we should place stubs code into the generated file.
set placestubs [expr {!$v::buildforpackage}]
# Determine the requested mode and reset for next call.
set buildforpackage $v::buildforpackage
set v::buildforpackage 0
if {$file eq ""} {
set file [This]
}
# NOTE: The 4 pieces of data just below has to be copied into the
# result even if the build and link-steps are suppressed. Because
# the load-step must have this information.
set shlib [DetermineShlibName $file]
set initname [DetermineInitName $file [expr {$buildforpackage ? "ns" : ""}]]
dict set v::code($file) result tsources [GetParam $file tsources]
dict set v::code($file) result mintcl [MinTclVersion $file]
if {$v::options(force) || ![file exists $shlib]} {
LogOpen $file
set base [BaseOf $file]
set object [DetermineObjectName $file]
API_setup $file
# Generate the main C file
CollectEmbeddedSources $file $base.c $object $initname $placestubs
# Set the marker for critcl::done and its user, AbortWhenCalledAfterBuild.
dict set v::code($file) result closed mark
# Compile main file
lappend objects [Compile $file $file $base.c $object]
# Compile the companion C sources as well, if there are any.
foreach src [GetParam $file csources] {
lappend objects [Compile $file $src $src \
[CompanionObject $src]]
}
# NOTE: The data below has to be copied into the result even
# if the link-step is suppressed. Because the application
# (mode 'generate package') must have this information to be
# able to perform the final link.
lappendlist objects [GetParam $file cobjects]
dict set v::code($file) result clibraries [GetParam $file clibraries]
dict set v::code($file) result ldflags [GetParam $file ldflags]
dict set v::code($file) result objects $objects
dict set v::code($file) result tk [UsingTk $file]
dict set v::code($file) result preload [GetParam $file preload]
dict set v::code($file) result license [GetParam $file license <<Undefined>>]
dict set v::code($file) result log {}
dict set v::code($file) result meta [GetMeta $file]
# Link and load steps.
if {$load || !$buildforpackage} {
Link $file
}
set msgs [LogClose]
dict set v::code($file) result warnings [CheckForWarnings $msgs]
}
if {$v::failed} {
if {!$buildforpackage} {
print stderr "$msgs\ncritcl build failed ($file)"
} else {
dict set v::code($file) result log $msgs
}
} elseif {$load && !$buildforpackage} {
Load $file
}
# Release the data which was collected for the just-built file, as
# it is not needed any longer.
dict unset v::code($file) config
return [StatusSave $file]
}
proc ::critcl::cresults {{file {}}} {
if {$file eq ""} { set file [This] }
return [dict get $v::code($file) result]
}
proc ::critcl::cnothingtodo {f} {
# No critcl definitions at all ?
if {![info exists v::code($f)]} { return 1 }
# We have results already, so where had been something to do.
if {[dict exists $v::code($f) result]} { return 0 }
# No C code collected for compilation ?
if {![dict exists $v::code($f) config fragments]} { return 1 }
# Ok, something has to be done.
return 0
}
proc ::critcl::c++command {tclname class constructors methods} {
# Build the body of the function to define a new tcl command for
# the C++ class
set helpline {}
set classptr ptr_$tclname
set comproc " $class* $classptr;\n"
append comproc " switch (objc) \{\n"
if {![llength $constructors]} {
set constructors {{}}
}
foreach adefs $constructors {
array set types {}
set names {}
set cargs {}
set cnames {}
foreach {t n} $adefs {
set types($n) $t
lappend names $n
lappend cnames _$n
lappend cargs "$t $n"
}
lappend helpline "$tclname pathName [join $names { }]"
set nargs [llength $names]
set ncargs [expr {$nargs + 2}]
append comproc " case $ncargs: \{\n"
if {!$nargs} {
append comproc " $classptr = new $class\();\n"
} else {
append comproc [ProcessArgs types $names $cnames]
append comproc " $classptr = new $class\([join $cnames {, }]);\n"
}
append comproc " break;\n"
append comproc " \}\n"
}
append comproc " default: \{\n"
append comproc " Tcl_SetResult(ip, \"wrong # args: should be either [join $helpline { or }]\",TCL_STATIC);\n"
append comproc " return TCL_ERROR;\n"
append comproc " \}\n"
append comproc " \}\n"
append comproc " if ( $classptr == NULL ) \{\n"
append comproc " Tcl_SetResult(ip, \"Not enough memory to allocate a new $tclname\", TCL_STATIC);\n"
append comproc " return TCL_ERROR;\n"
append comproc " \}\n"
append comproc " Tcl_CreateObjCommand(ip, Tcl_GetString(objv\[1]), cmdproc_$tclname, (ClientData) $classptr, delproc_$tclname);\n"
append comproc " return TCL_OK;\n"
#
# Build the body of the c function called when the object is deleted
#
set delproc "void delproc_$tclname\(ClientData cd) \{\n"
append delproc " if (cd != NULL)\n"
append delproc " delete ($class*) cd;\n"
append delproc "\}\n"
#
# Build the body of the function that processes the tcl commands for the class
#
set cmdproc "int cmdproc_$tclname\(ClientData cd, Tcl_Interp* ip, int objc, Tcl_Obj *CONST objv\[]) \{\n"
append cmdproc " int index;\n"
append cmdproc " $class* $classptr = ($class*) cd;\n"
set rtypes {}
set tnames {}
set mnames {}
set adefs {}
foreach {rt n a} $methods {
lappend rtypes $rt
lappend tnames [lindex $n 0]
set tmp [lindex $n 1]
if {$tmp eq ""} {
lappend mnames [lindex $n 0]
} else {
lappend mnames [lindex $n 1]
}
lappend adefs $a
}
append cmdproc " static const char* cmds\[]=\{\"[join $tnames {","}]\",NULL\};\n"
append cmdproc " if (objc<2) \{\n"
append cmdproc " Tcl_WrongNumArgs(ip, 1, objv, \"expecting pathName option\");\n"
append cmdproc " return TCL_ERROR;\n"
append cmdproc " \}\n\n"
append cmdproc " if (Tcl_GetIndexFromObj(ip, objv\[1], cmds, \"option\", TCL_EXACT, &index) != TCL_OK)\n"
append cmdproc " return TCL_ERROR;\n"
append cmdproc " switch (index) \{\n"
set ndx 0
foreach rtype $rtypes tname $tnames mname $mnames adef $adefs {
array set types {}
set names {}
set cargs {}
set cnames {}
switch -- $rtype {
ok { set rtype2 "int" }
string -
dstring -
vstring { set rtype2 "char*" }
default { set rtype2 $rtype }
}
foreach {t n} $adef {
set types($n) $t
lappend names $n
lappend cnames _$n
lappend cargs "$t $n"
}
set helpline "$tname [join $names { }]"
set nargs [llength $names]
set ncargs [expr {$nargs + 2}]
append cmdproc " case $ndx: \{\n"
append cmdproc " if (objc==$ncargs) \{\n"
append cmdproc [ProcessArgs types $names $cnames]
append cmdproc " "
if {$rtype ne "void"} {
append cmdproc "$rtype2 rv = "
}
append cmdproc "$classptr->$mname\([join $cnames {, }]);\n"
append cmdproc " "
switch -- $rtype {
void { }
ok { append cmdproc "return rv;" }
int { append cmdproc "Tcl_SetIntObj(Tcl_GetObjResult(ip), rv);" }
long { append cmdproc " Tcl_SetLongObj(Tcl_GetObjResult(ip), rv);" }
float -
double { append cmdproc "Tcl_SetDoubleObj(Tcl_GetObjResult(ip), rv);" }
char* { append cmdproc "Tcl_SetResult(ip, rv, TCL_STATIC);" }
string -
dstring { append cmdproc "Tcl_SetResult(ip, rv, TCL_DYNAMIC);" }
vstring { append cmdproc "Tcl_SetResult(ip, rv, TCL_VOLATILE);" }
default { append cmdproc "if (rv == NULL) \{ return TCL_ERROR ; \}\n Tcl_SetObjResult(ip, rv); Tcl_DecrRefCount(rv);" }
}
append cmdproc "\n"
append cmdproc " "
if {$rtype ne "ok"} { append cmdproc "return TCL_OK;\n" }
append cmdproc " \} else \{\n"
append cmdproc " Tcl_WrongNumArgs(ip, 1, objv, \"$helpline\");\n"
append cmdproc " return TCL_ERROR;\n"
append cmdproc " \}\n"
append cmdproc " \}\n"
incr ndx
}
append cmdproc " \}\n\}\n"
# TODO: line pragma fix ?!
ccode $delproc
ccode $cmdproc
# Force the new ccommand to be defined in the caller's namespace
# instead of improperly in ::critcl.
namespace eval [uplevel 1 namespace current] \
[list critcl::ccommand $tclname {dummy ip objc objv} $comproc]
return
}
proc ::critcl::ProcessArgs {typesArray names cnames} {
upvar 1 $typesArray types
set body ""
foreach x $names c $cnames {
set t $types($x)
switch -- $t {
int - long - float - double - char* - Tcl_Obj* {
append body " $t $c;\n"
}
default {
append body " void* $c;\n"
}
}
}
set n 1
foreach x $names c $cnames {
set t $types($x)
incr n
switch -- $t {
int {
append body " if (Tcl_GetIntFromObj(ip, objv\[$n], &$c) != TCL_OK)\n"
append body " return TCL_ERROR;\n"
}
long {
append body " if (Tcl_GetLongFromObj(ip, objv\[$n], &$c) != TCL_OK)\n"
append body " return TCL_ERROR;\n"
}
float {
append body " \{ double tmp;\n"
append body " if (Tcl_GetDoubleFromObj(ip, objv\[$n], &tmp) != TCL_OK)\n"
append body " return TCL_ERROR;\n"
append body " $c = (float) tmp;\n"
append body " \}\n"
}
double {
append body " if (Tcl_GetDoubleFromObj(ip, objv\[$n], &$c) != TCL_OK)\n"
append body " return TCL_ERROR;\n"
}
char* {
append body " $c = Tcl_GetString(objv\[$n]);\n"
}
default {
append body " $c = objv\[$n];\n"
}
}
}
return $body
}
proc ::critcl::scan {file} {
set lines [split [Cat $file] \n]
set scan::rkey require
set scan::base [file dirname [file normalize $file]]
set scan::capture {
org {}
version {}
files {}
imported {}
config {}
meta-user {}
meta-system {}
tsources {}
}
ScanCore $lines {
critcl::api sub
critcl::api/extheader ok
critcl::api/function ok
critcl::api/header warn
critcl::api/import ok
critcl::source warn
critcl::cheaders warn
critcl::csources warn
critcl::license warn
critcl::meta warn
critcl::owns warn
critcl::tcl ok
critcl::tk ok
critcl::tsources warn
critcl::userconfig sub
critcl::userconfig/define ok
critcl::userconfig/query ok
critcl::userconfig/set ok
package warn
}
set version [dict get $scan::capture version]
print "\tVersion: $version"
# TODO : Report requirements.
# TODO : tsources - Scan files for dependencies!
set n [llength [dict get $scan::capture files]]
print -nonewline "\tInput: $file"
if {$n} {
print -nonewline " + $n Companion"
if {$n > 1} { print -nonewline s }
}
print ""
# Merge the system and user meta data, with system overriding the
# user. See 'GetMeta' for same operation when actually builing the
# package. Plus scan any Tcl companions for more requirements.
set md {}
lappend md [dict get $scan::capture meta-user]
lappend md [dict get $scan::capture meta-system]
foreach ts [dict get $scan::capture tsources] {
lappend md [dict get [ScanDependencies $file \
[file join [file dirname $file] $ts] \
capture] meta-system]
}
dict unset scan::capture meta-user
dict unset scan::capture meta-system
dict unset scan::capture tsources
dict set scan::capture meta \
[eval [linsert $md 0 dict merge]]
# meta = dict merge {*}$md
if {[dict exists $scan::capture meta require]} {
foreach r [dict get $scan::capture meta require] {
print "\tRequired: $r"
}
}
return $scan::capture
}
proc ::critcl::ScanDependencies {dfile file {mode plain}} {
set lines [split [Cat $file] \n]
catch {
set saved $scan::capture
}
set scan::rkey require
set scan::base [file dirname [file normalize $file]]
set scan::capture {
name {}
version {}
meta-system {}
}
ScanCore $lines {
critcl::buildrequirement warn
package warn
}
if {$mode eq "capture"} {
set result $scan::capture
set scan::capture $saved
return $result
}
dict with scan::capture {
if {$mode eq "provide"} {
msg -nonewline " (provide $name $version)"
ImetaSet $dfile name $name
ImetaSet $dfile version $version
}
dict for {k vlist} [dict get $scan::capture meta-system] {
if {$k eq "name"} continue
if {$k eq "version"} continue
ImetaAdd $dfile $k $vlist
if {$k ne "require"} continue
msg -nonewline " ($k [join $vlist {}])"
}
# The above information also goes into the teapot meta data of
# the file in question. This however is defered until the meta
# data is actually pulled for delivery to the tool using the
# package. See 'GetMeta' for where the merging happens.
}
return
}
proc critcl::ScanCore {lines theconfig} {
# config = dictionary
# - <cmdname> => mode (ok, warn, sub)
# Unlisted commands are ignored.
variable scan::config $theconfig
set collect 0
set buf {}
set lno -1
foreach line $lines {
#puts |$line|
incr lno
if {$collect} {
if {![info complete $buf]} {
append buf $line \n
continue
}
set collect 0
#puts %%$buf%%
# Prevent heavily dynamic code from stopping the scan.
# WARN the user.
regexp {^(\S+)} $buf -> cmd
if {[dict exists $config $cmd]} {
set mode [dict get $config $cmd]
if {[catch {
# Run in the scan namespace, with its special
# command implementations.
namespace eval ::critcl::scan $buf
} msg]} {
if {$mode eq "sub"} {
regexp {^(\S+)\s+(\S+)} $buf -> _ method
append cmd /$method
set mode [dict get $config $cmd]
}
if {$mode eq "warn"} {
msg "Line $lno, $cmd: Failed execution of dynamic command may"
msg "Line $lno, $cmd: cause incorrect TEA results. Please check."
msg "Line $lno, $cmd: $msg"
}
}
}
set buf ""
# fall through, to handle the line which just got NOT
# added to the buf.
}
set line [string trimleft $line " \t:"]
if {[string trim $line] eq {}} continue
regexp {^(\S+)} $line -> cmd
if {[dict exists $config $cmd]} {
append buf $line \n
set collect 1
}
}
}
# Handle the extracted commands
namespace eval ::critcl::scan::critcl {}
proc ::critcl::scan::critcl::buildrequirement {script} {
# Recursive scan of the script, same configuration, except
# switched to record 'package require's under the build::reqire
# key.
variable ::critcl::scan::config
variable ::critcl::scan::rkey
set saved $rkey
set rkey build::require
::critcl::ScanCore [split $script \n] $config
set rkey $saved
return
}
# Meta data.
# Capture specific dependencies
proc ::critcl::scan::critcl::tcl {version} {
variable ::critcl::scan::capture
dict update capture meta-system m {
dict lappend m require [list Tcl $version]
}
return
}
proc ::critcl::scan::critcl::tk {} {
variable ::critcl::scan::capture
dict update capture meta-system m {
dict lappend m require Tk
}
return
}
proc ::critcl::scan::critcl::description {text} {
variable ::critcl::scan::capture
dict set capture meta-system description \
[::critcl::Text2Words $text]
return
}
proc ::critcl::scan::critcl::summary {text} {
variable ::critcl::scan::capture
dict set capture meta-system summary \
[::critcl::Text2Words $text]
return
}
proc ::critcl::scan::critcl::subject {args} {
variable ::critcl::scan::capture
dict update capture meta-system m {
foreach word $args {
dict lappend m subject $word
}
}
return
}
proc ::critcl::scan::critcl::meta {key args} {
variable ::critcl::scan::capture
dict update capture meta-user m {
foreach word $args {
dict lappend m $key $word
}
}
return
}
# Capture files
proc ::critcl::scan::critcl::source {path} {
# Recursively scan the imported file.
# Keep the current context.
variable ::critcl::scan::config
foreach f [Files $path] {
set lines [split [::critcl::Cat $f] \n]
ScanCore $lines $config
}
return
}
proc ::critcl::scan::critcl::owns {args} { eval [linsert $args 0 Files] }
proc ::critcl::scan::critcl::cheaders {args} { eval [linsert $args 0 Files] }
proc ::critcl::scan::critcl::csources {args} { eval [linsert $args 0 Files] }
proc ::critcl::scan::critcl::tsources {args} {
variable ::critcl::scan::capture
foreach ts [eval [linsert $args 0 Files]] {
dict lappend capture tsources $ts
}
return
}
proc ::critcl::scan::critcl::Files {args} {
variable ::critcl::scan::capture
set res {}
foreach v $args {
if {[string match "-*" $v]} continue
foreach f [Expand $v] {
dict lappend capture files $f
lappend res $f
}
}
return $res
}
proc ::critcl::scan::critcl::Expand {pattern} {
variable ::critcl::scan::base
# Note: We cannot use -directory here. The PATTERN may already be
# an absolute path, in which case the join will return the
# unmodified PATTERN to glob on, whereas with -directory the final
# pattern will be BASE/PATTERN which won't find anything, even if
# PATTERN actually exists.
set prefix [file split $base]
set files {}
foreach vfile [glob [file join $base $pattern]] {
set xfile [file normalize $vfile]
if {![file exists $xfile]} {
error "$vfile: not found"
}
# Constrain to be inside of the base directory.
# Snarfed from fileutil::stripPath
set npath [file split $xfile]
if {![string match -nocase "${prefix} *" $npath]} {
error "$vfile: Not inside of $base"
}
set xfile [eval [linsert [lrange $npath [llength $prefix] end] 0 file join ]]
lappend files $xfile
}
return $files
}
# Capture license (org name)
proc ::critcl::scan::critcl::license {who args} {
variable ::critcl::scan::capture
dict set capture org $who
print "\tOrganization: $who"
# Meta data.
set elicense [::critcl::LicenseText $args]
dict set capture meta-system license \
[::critcl::Text2Words $elicense]
dict set capture meta-system author \
[::critcl::Text2Authors $who]
return
}
# Capture version of the provided package.
proc ::critcl::scan::package {cmd args} {
if {$cmd eq "provide"} {
# Syntax: package provide <name> <version>
variable capture
lassign $args name version
dict set capture name $name
dict set capture version $version
# Save as meta data as well.
dict set capture meta-system name $name
dict set capture meta-system version $version
dict set capture meta-system platform source
dict set capture meta-system generated::by \
[list \
[list critcl [::package present critcl]] \
$::tcl_platform(user)]
dict set capture meta-system generated::date \
[list [clock format [clock seconds] -format {%Y-%m-%d}]]
return
} elseif {$cmd eq "require"} {
# Syntax: package require <name> ?-exact? <version>
# : package require <name> <version-range>...
# Save dependencies as meta data.
# Ignore the critcl core
if {[lindex $args 0] eq "critcl"} return
variable capture
variable rkey
dict update capture meta-system m {
dict lappend m $rkey [::critcl::TeapotRequire $args]
}
return
}
# ignore anything else.
return
}
# Capture the APIs imported by the package
proc ::critcl::scan::critcl::api {cmd args} {
variable ::critcl::scan::capture
switch -exact -- $cmd {
header {
eval [linsert $args 0 Files]
}
import {
# Syntax: critcl::api import <name> <version>
lassign $args name _
dict lappend capture imported $name
print "\tImported: $name"
}
default {}
}
return
}
# Capture the user config options declared by the package
proc ::critcl::scan::critcl::userconfig {cmd args} {
variable ::critcl::scan::capture
switch -exact -- $cmd {
define {
# Syntax: critcl::userconfig define <name> <description> <type> ?<default>?
lassign $args oname odesc otype odefault
set odesc [string trim $odesc]
if {[llength $args] < 4} {
set odefault [::critcl::UcDefault $otype]
}
dict lappend capture config [list $oname $odesc $otype $odefault]
print "\tUser Config: $oname ([join $otype { }] -> $odefault) $odesc"
}
set - query -
default {}
}
return
}
# # ## ### ##### ######## ############# #####################
## Implementation -- Internals - cproc conversion helpers.
proc ::critcl::EmitShimHeader {wname} {
# Function head
set ca "(ClientData cd, Tcl_Interp *interp, int oc, Tcl_Obj *CONST ov\[])"
Emitln
Emitln "static int"
Emitln "$wname$ca"
Emitln \{
return
}
proc ::critcl::EmitShimVariables {adefs rtype} {
set opt 0
foreach d [argvardecls $adefs] o [argoptional $adefs] {
Emitln " $d"
if {$o} {set opt 1}
}
if {$opt} { Emitln " int idx_;" }
# Result variable, source for the C -> Tcl conversion.
if {$rtype ne "void"} { Emit " [ResultCType $rtype] rv;" }
return
}
proc ::critcl::EmitWrongArgsCheck {adefs offset} {
# Code checking for the correct count of arguments, and generating
# the proper error if not.
# A 1st argument matching "Tcl_Interp*" does not count as a user
# visible command argument.
if {[lindex $adefs 0] eq "Tcl_Interp*"} {
set adefs [lrange $adefs 2 end]
}
set min 0 ; # count all non-optional argument. min required.
set max 0 ; # count all arguments. max allowed.
set names {}
foreach {t a} $adefs {
incr max
if {[llength $a] == 1} {
incr min
lappend names $a
} else {
lappend names ?[lindex $a 0]?
}
}
incr min
incr max
incr min $offset
incr max $offset
set keep 1
incr keep $offset
set names [join $names { }]
if {$names eq {}} {
set names NULL
} else {
set names \"$names\"
}
Emitln ""
if {$min == $max} {
Emitln " if (oc != $min) \{"
} else {
Emitln " if ((oc < $min) || ($max < oc)) \{"
}
Emitln " Tcl_WrongNumArgs(interp, $keep, ov, $names);"
Emitln " return TCL_ERROR;"
Emitln " \}"
Emitln ""
return
}
proc ::critcl::EmitArgumentConversion {adefs offset} {
incr offset
foreach c [argconversion $adefs $offset] {
Emitln $c
}
return
}
proc ::critcl::EmitCall {cname cnames rtype} {
# Invoke the low-level function.
Emitln " /* Call - - -- --- ----- -------- */"
Emit " "
if {$rtype ne "void"} { Emit "rv = " }
Emitln "${cname}([join $cnames {, }]);"
Emitln
return
}
proc ::critcl::EmitShimFooter {rtype} {
# Convert the returned low-level result from C to Tcl, if required.
# Return a standard status, if required.
set code [ResultConversion $rtype]
if {$code ne {}} { Emitln $code }
Emitln \}
return
}
proc ::critcl::ArgumentSupport {type} {
if {[info exists v::acsup($type)]} { return $v::acsup($type) }
return {}
}
proc ::critcl::ArgumentCType {type} {
if {[info exists v::actype($type)]} { return $v::actype($type) }
return -code error "Unknown argument type $type"
}
proc ::critcl::ArgumentCTypeB {type} {
if {[info exists v::actypeb($type)]} { return $v::actypeb($type) }
return -code error "Unknown argument type $type"
}
proc ::critcl::ArgumentConversion {type} {
if {[info exists v::aconv($type)]} { return $v::aconv($type) }
return -code error "Unknown argument type $type"
}
proc ::critcl::ResultCType {type} {
if {[info exists v::rctype($type)]} {
return $v::rctype($type)
}
return -code error "Unknown result type $type"
}
proc ::critcl::ResultConversion {type} {
if {[info exists v::rconv($type)]} {
return $v::rconv($type)
}
return -code error "Unknown result type $type"
}
# # ## ### ##### ######## ############# #####################
## Implementation -- Internals - Manage complex per-file settings.
proc ::critcl::GetParam {file type {default {}}} {
if {[info exists v::code($file)] &&
[dict exists $v::code($file) config $type]} {
return [dict get $v::code($file) config $type]
} else {
return $default
}
}
proc ::critcl::SetParam {type values {expand 1} {uuid 0}} {
set file [This]
if {![llength $values]} return
UUID.extend $file .$type $values
if {[llength $values]} {
# Process the list of flags, treat non-option arguments as
# glob patterns and expand them to a set of files, stored as
# absolute paths.
set tmp {}
foreach v $values {
if {[string match "-*" $v]} {
lappend tmp $v
} else {
if {$expand} {
if {$uuid} {
foreach f [Expand $file $v] {
lappend tmp $f
UUID.extend $file .$type.$f [Cat $f]
}
} else {
lappendlist tmp [Expand $file $v]
}
} else {
lappend tmp $v
}
}
}
# And save into the system state.
dict update v::code($file) config c {
foreach v $tmp {
dict lappend c $type $v
}
}
} elseif {[dict exists $v::code($file) config $type]} {
return [dict get $v::code($file) config $type]
}
}
proc ::critcl::Expand {file pattern} {
set base [file dirname $file]
# Note: We cannot use -directory here. The PATTERN may already be
# an absolute path, in which case the join will return the
# unmodified PATTERN to glob on, whereas with -directory the final
# pattern will be BASE/PATTERN which won't find anything, even if
# PATTERN actually exists.
set files {}
foreach vfile [glob [file join $base $pattern]] {
set vfile [file normalize $vfile]
if {![file exists $vfile]} {
error "$vfile: not found"
}
lappend files $vfile
}
return $files
}
proc ::critcl::InitializeFile {file} {
if {![info exists v::code($file)]} {
set v::code($file) {}
# Initialize the meta data sections (user (meta) and system
# (package)).
dict set v::code($file) config meta {}
dict set v::code($file) config package platform \
[TeapotPlatform]
dict set v::code($file) config package build::date \
[list [clock format [clock seconds] -format {%Y-%m-%d}]]
# May not exist, bracket code.
if {![file exists $file]} return
ScanDependencies $file $file provide
return
}
if {![dict exists $v::code($file) config]} {
dict set v::code($file) config {}
}
return
}
# # ## ### ##### ######## ############# #####################
## Implementation -- Internals - Management of in-memory C source fragment.
proc ::critcl::name2c {name} {
# Note: A slightly modified copy (different depth in the call-stack) of this
# is inlined into the internal command "BeginCommand".
# Locate caller, as the data is saved per .tcl file.
set file [This]
if {![string match ::* $name]} {
# Locate caller's namespace. Two up, skipping the
# ccommand/cproc frame. This is where the new Tcl command will
# be defined in.
set ns [uplevel 1 namespace current]
if {$ns ne "::"} { append ns :: }
set name ${ns}$name
}
# First ensure that any namespace qualifiers found in the name
# itself are shifted over to the namespace information.
set ns [namespace qualifiers $name]
set name [namespace tail $name]
# Then ensure that everything is fully qualified, and that the C
# level name doesn't contain bad characters. We have to remove any
# non-alphabetic characters. A serial number is further required
# to distinguish identifiers which would, despite having different
# Tcl names, transform to the same C identifier.
if {$ns ne "::"} { append ns :: }
set cns [string map {:: _} $ns]
regsub -all -- {[^a-zA-Z0-9_]} $name _ cname
regsub -all -- {_+} $cname _ cname
regsub -all -- {[^a-zA-Z0-9_]} $cns _ cns
regsub -all -- {_+} $cns _ cns
set cname $cname[UUID.serial $file]
return [list $ns $cns $name $cname]
}
proc ::critcl::BeginCommand {visibility name args} {
# Locate caller, as the data is saved per .tcl file.
set file [This]
# Inlined name2c
if {![string match ::* $name]} {
# Locate caller's namespace. Two up, skipping the
# ccommand/cproc frame. This is where the new Tcl command will
# be defined in.
set ns [uplevel 2 namespace current]
if {$ns ne "::"} { append ns :: }
set name ${ns}$name
}
# First ensure that any namespace qualifiers found in the name
# itself are shifted over to the namespace information.
set ns [namespace qualifiers $name]
set name [namespace tail $name]
# Then ensure that everything is fully qualified, and that the C
# level identifiers don't contain bad characters. We have to
# remove any non-alphabetic characters. A serial number is further
# required to distinguish identifiers which would, despite having
# different Tcl names, transform to the same C identifier.
if {$ns ne "::"} { append ns :: }
set cns [string map {:: _} $ns]
regsub -all -- {[^a-zA-Z0-9_]} $name _ cname
regsub -all -- {_+} $cname _ cname
regsub -all -- {[^a-zA-Z0-9_]} $cns _ cns
regsub -all -- {_+} $cns _ cns
set cname $cname[UUID.serial $file]
# Set the defered build-on-demand used by mode 'comile & run' up.
# Note: Removing the leading :: because it trips Tcl's unknown
# command, i.e. the command will not be found when called in a
# script without leading ::.
set ::auto_index([string trimleft $ns$name :]) [list [namespace current]::cbuild $file]
set v::curr [UUID.extend $file .function "$ns $name $args"]
dict update v::code($file) config c {
dict lappend c functions $cns$cname
dict lappend c fragments $v::curr
}
if {$visibility eq "public"} {
Emitln "#define ns_$cns$cname \"$ns$name\""
}
return [list $ns $cns $name $cname]
}
proc ::critcl::EndCommand {} {
set file [This]
set v::code($v::curr) $v::block
dict set v::code($file) config block $v::curr $v::block
unset v::curr
unset v::block
return
}
proc ::critcl::Emit {s} {
append v::block $s
return
}
proc ::critcl::Emitln {{s ""}} {
Emit $s\n
return
}
# # ## ### ##### ######## ############# #####################
## At internal processing
proc ::critcl::at::Where {leadoffset level file} {
variable where
set line 1
# If the interpreter running critcl has TIP 280 support use it to
# place more exact line number information into the generated C
# file.
#puts "XXX-WHERE-($leadoffset $level $file)"
#set ::errorInfo {}
if {[catch {
#SHOWFRAMES $level 0
array set loc [info frame $level]
#puts XXX-TYPE-$loc(type)
}]} {
#puts XXX-NO-DATA-$::errorInfo
set where {}
return
}
if {$loc(type) eq "source"} {
#parray loc
set file $loc(file)
set fline $loc(line)
# Adjust for removed leading whitespace.
::incr fline $leadoffset
# Keep the limitations of native compilers in mind and stay
# inside their bounds.
if {$fline > $line} {
set line $fline
}
set where [list [file tail $file] $line]
return
}
if {($loc(type) eq "eval") &&
[info exists loc(proc)] &&
($loc(proc) eq "::critcl::source")
} {
# A relative location in critcl::source is absolute in the
# sourced file. I.e. we can provide proper line information.
set fline $loc(line)
# Adjust for removed leading whitespace.
::incr fline $leadoffset
# Keep the limitations of native compilers in mind and stay
# inside their bounds.
if {$fline > $line} {
set line $fline
}
variable ::critcl::v::source
set where [list [file tail $source] $line]
return
}
#puts XXX-NO-DATA-$loc(type)
set where {}
return
}
proc ::critcl::at::CPragma {leadoffset level file} {
# internal variant of 'caller!'
::incr level -1
Where $leadoffset $level $file
return [get]
}
proc ::critcl::at::Format {loc} {
if {![llength $loc]} {
return ""
}
lassign $loc file line
#::critcl::msg "#line $line \"$file\"\n"
return "#line $line \"$file\"\n"
}
proc ::critcl::at::SHOWFRAMES {level {all 1}} {
set n [info frame]
set i 0
set id 1
while {$n} {
::critcl::msg "[expr {$level == $id ? "**" : " "}] frame [format %3d $id]: [info frame $i]"
::incr i -1
::incr id -1
::incr n -1
if {($level > $id) && !$all} break
}
return
}
# # ## ### ##### ######## ############# #####################
proc ::critcl::CollectEmbeddedSources {file destination libfile ininame placestubs} {
set fd [open $destination w]
if {[dict exists $v::code($file) result apiprefix]} {
set api [dict get $v::code($file) result apiprefix]
} else {
set api ""
}
# Boilerplate header.
puts $fd [subst [Cat [Template header.c]]]
# ^=> file, libfile, api
# Make Tk available, if requested
if {[UsingTk $file]} {
puts $fd "\n#include \"tk.h\""
}
# Write the collected C fragments, in order of collection.
foreach digest [GetParam $file fragments] {
puts $fd "[Separator]\n"
puts $fd [dict get $v::code($file) config block $digest]
}
# Boilerplate trailer.
# Stubs setup, Tcl, and, if requested, Tk as well.
puts $fd [Separator]
set mintcl [MinTclVersion $file]
if {$placestubs} {
# Put full stubs definitions into the code, which can be
# either the bracket generated for a -pkg, or the package
# itself, build in mode "compile & run".
set stubs [TclDecls $file]
set platstubs [TclPlatDecls $file]
puts -nonewline $fd [subst [Cat [Template stubs.c]]]
# ^=> mintcl, stubs, platstubs
} else {
# Declarations only, for linking, in the sub-packages.
puts -nonewline $fd [subst [Cat [Template stubs_e.c]]]
# ^=> mintcl
}
if {[UsingTk $file]} {
SetupTkStubs $fd
}
# Initialization boilerplate. This ends in the middle of the
# FOO_Init() function, leaving it incomplete.
set ext [GetParam $file edecls]
puts $fd [subst [Cat [Template pkginit.c]]]
# ^=> ext, ininame
# From here on we are completing FOO_Init().
# Tk setup first, if requested. (Tcl is already done).
if {[UsingTk $file]} {
puts $fd [Cat [Template pkginittk.c]]
}
# User specified initialization code.
puts $fd "[GetParam $file initc] "
# Setup of the variables serving up defined constants.
if {[dict exists $v::code($file) config const]} {
BuildDefines $fd $file
}
# Take the names collected earlier and register them as Tcl
# commands.
foreach name [lsort [GetParam $file functions]] {
if {[info exists v::clientdata($name)]} {
set cd $v::clientdata($name)
} else {
set cd NULL
}
if {[info exists v::delproc($name)]} {
set dp $v::delproc($name)
} else {
set dp 0
}
puts $fd " Tcl_CreateObjCommand(ip, ns_$name, tcl_$name, $cd, $dp);"
}
# Complete the trailer and be done.
puts $fd [Cat [Template pkginitend.c]]
close $fd
return
}
proc ::critcl::MinTclVersion {file} {
set required [GetParam $file mintcl 8.4]
foreach version $v::hdrsavailable {
if {[package vsatisfies $version $required]} {
return $version
}
}
return $required
}
proc ::critcl::UsingTk {file} {
return [GetParam $file tk 0]
}
proc ::critcl::TclIncludes {file} {
# Provide access to the Tcl/Tk headers using a -I flag pointing
# into the critcl package directory hierarchy. No copying of files
# required. This also handles the case of the X11 headers on
# windows, for free.
set hdrs tcl[MinTclVersion $file]
set path [file join $v::hdrdir $hdrs]
if {[file system $path] ne "native"} {
# The critcl package is wrapped. Copy the relevant headers out
# to disk and change the include path appropriately.
Copy $path $v::cache
set path [file join $v::cache $hdrs]
}
return [list $c::include$path]
}
proc ::critcl::TclHeader {file {header {}}} {
# Provide access to the Tcl/Tk headers in the critcl package
# directory hierarchy. No copying of files required.
set hdrs tcl[MinTclVersion $file]
return [file join $v::hdrdir $hdrs $header]
}
proc ::critcl::SystemIncludes {file} {
set includes {}
foreach dir [SystemIncludePaths $file] {
lappend includes $c::include$dir
}
return $includes
}
proc ::critcl::SystemIncludePaths {file} {
set paths {}
set has {}
# critcl -I options.
foreach dir $v::options(I) {
if {[dict exists $has $dir]} continue
dict set has $dir yes
lappend paths $dir
}
# Result cache.
lappend paths $v::cache
# critcl::cheaders
foreach flag [GetParam $file cheaders] {
if {![string match "-*" $flag]} {
# flag = normalized absolute path to a header file.
# Transform into a -I directory reference.
set dir [file dirname $flag]
} else {
# Chop leading -I
set dir [string range $flag 2 end]
}
if {[dict exists $has $dir]} continue
dict set has $dir yes
lappend paths $dir
}
return $paths
}
proc ::critcl::SystemLibraries {} {
set libincludes {}
foreach dir [SystemLibraryPaths] {
lappend libincludes $c::libinclude$dir
}
return $libincludes
}
proc ::critcl::SystemLibraryPaths {} {
set paths {}
set has {}
# critcl -L options.
foreach dir $v::options(L) {
if {[dict exists $has $dir]} continue
dict set has $dir yes
lappend paths $dir
}
return $paths
}
proc ::critcl::Compile {tclfile origin cfile obj} {
StatusAbort?
# tclfile = The .tcl file under whose auspices the C is compiled.
# origin = The origin of the C sources, either tclfile, or cfile.
# cfile = The file holding the C sources to compile.
#
# 'origin == cfile' for the companion C files of a critcl file,
# i.e. the csources. For a .tcl critcl file, the 'origin ==
# tclfile', and the cfile is the .c derived from tclfile.
#
# obj = Object file to compile to, to generate.
set cmdline [getconfigvalue compile]
lappendlist cmdline [GetParam $tclfile cflags]
lappendlist cmdline [getconfigvalue threadflags]
if {$v::options(combine) ne "standalone"} {
lappendlist cmdline [getconfigvalue tclstubs]
}
if {$v::options(language) ne "" && [file tail $tclfile] ne "critcl.tcl"} {
# XXX Is this gcc specific ?
# XXX Should this not be configurable via some c::* setting ?
# See also -x none below.
lappend cmdline -x $v::options(language)
}
lappendlist cmdline [TclIncludes $tclfile]
lappendlist cmdline [SystemIncludes $tclfile]
if {[dict exists $v::code($tclfile) result apidefines]} {
lappendlist cmdline [dict get $v::code($tclfile) result apidefines]
}
lappendlist cmdline [CompileResult $obj]
lappend cmdline $cfile
if {$v::options(language) ne ""} {
# Allow the compiler to determine the type of file otherwise
# it will try to compile the libs
# XXX Is this gcc specific ?
# XXX Should this not be configurable via some c::* setting ?
lappend cmdline -x none
}
# Add the Tk stubs to the command line, if requested and not suppressed
if {[UsingTk $tclfile] && ($v::options(combine) ne "standalone")} {
lappendlist cmdline [getconfigvalue tkstubs]
}
if {!$option::debug_symbols} {
lappendlist cmdline [getconfigvalue optimize]
lappendlist cmdline [getconfigvalue noassert]
}
if {[ExecWithLogging $cmdline \
{$obj: [file size $obj] bytes} \
{ERROR while compiling code in $origin:}]} {
if {!$v::options(keepsrc) && $cfile ne $origin} {
file delete $cfile
}
}
return $obj
}
proc ::critcl::MakePreloadLibrary {file} {
StatusAbort?
# compile and link the preload support, if necessary, i.e. not yet
# done.
set shlib [file join $v::cache preload[getconfigvalue sharedlibext]]
if {[file exists $shlib]} return
# Operate like TclIncludes. Use the template file directly, if
# possible, or, if we reside in a virtual filesystem, copy it to
# disk.
set src [Template preload.c]
if {[file system $src] ne "native"} {
file mkdir $v::cache
file copy -force $src $v::cache
set src [file join $v::cache preload.c]
}
# Build the object for the helper package, 'preload' ...
set obj [file join $v::cache preload.o]
Compile $file $src $src $obj
# ... and link it.
# Custom linker command. XXX Can we bent Link to the task?
set cmdline [getconfigvalue link]
lappend cmdline $obj
lappendlist cmdline [getconfigvalue strip]
lappendlist cmdline [LinkResult $shlib]
ExecWithLogging $cmdline \
{$shlib: [file size $shlib] bytes} \
{ERROR while linking $shlib:}
# Now the critcl application can pick up this helper shlib and
# stuff it into the package it is making.
return
}
proc ::critcl::Link {file} {
StatusAbort?
set shlib [dict get $v::code($file) result shlib]
set preload [dict get $v::code($file) result preload]
# Assemble the link command.
set cmdline [getconfigvalue link]
if {[llength $preload]} {
lappendlist cmdline [getconfigvalue link_preload]
}
if {$option::debug_symbols} {
lappendlist cmdline [getconfigvalue link_debug]
} else {
lappendlist cmdline [getconfigvalue strip]
lappendlist cmdline [getconfigvalue link_release]
}
lappendlist cmdline [LinkResult $shlib]
lappendlist cmdline [GetObjects $file]
lappendlist cmdline [SystemLibraries]
lappendlist cmdline [GetLibraries $file]
lappendlist cmdline [dict get $v::code($file) result ldflags]
# lappend cmdline bufferoverflowU.lib ;# msvc >=1400 && <1500 for amd64
# Run the linker
ExecWithLogging $cmdline \
{$shlib: [file size $shlib] bytes} \
{ERROR while linking $shlib:}
# Now, if there is a manifest file around, and the
# 'embed_manifest' command defined we use its command to merge the
# manifest into the shared library. This is pretty much only
# happening on Windows platforms, and with newer dev environments
# actually using manifests.
set em [getconfigvalue embed_manifest]
critcl::Log "Manifest Command: $em"
critcl::Log "Manifest File: [expr {[file exists $shlib.manifest]
? "$shlib.manifest"
: "<<not present>>, ignored"}]"
if {[llength $em] && [file exists $shlib.manifest]} {
set cmdline [ManifestCommand $em $shlib]
# Run the manifest tool
ExecWithLogging $cmdline \
{$shlib: [file size $shlib] bytes, with manifest} \
{ERROR while embedding the manifest into $shlib:}
}
# At last, build the preload support library, if necessary.
if {[llength $preload]} {
MakePreloadLibrary $file
}
return
}
proc ::critcl::ManifestCommand {em shlib} {
# Variable used by the subst'able config setting.
set outfile $shlib
return [subst $em]
}
proc ::critcl::CompanionObject {src} {
set tail [file tail $src]
set srcbase [file rootname $tail]
if {$v::cache ne [file dirname $src]} {
set srcbase [file tail [file dirname $src]]_$srcbase
}
return [file join $v::cache ${srcbase}[getconfigvalue object]]
}
proc ::critcl::CompileResult {object} {
# Variable used by the subst'able config setting.
set outfile $object
return [subst $c::output]
}
proc ::critcl::LinkResult {shlib} {
# Variable used by the subst'able config setting.
set outfile $shlib
set ldout [subst $c::ldoutput]
if {$ldout eq ""} {
set ldout [subst $c::output]
}
return $ldout
}
proc ::critcl::GetObjects {file} {
# On windows using the native MSVC compiler put the companion
# object files into a link file to read, instead of separately on
# the command line.
set objects [dict get $v::code($file) result objects]
if {![string match "win32-*-cl" $v::buildplatform]} {
return $objects
}
set rsp [WriteCache link.fil \"[join $objects \"\n\"]\"]
return [list @$rsp]
}
proc ::critcl::GetLibraries {file} {
# On windows using the native MSVC compiler, transform all -lFOO
# references into FOO.lib.
return [FixLibraries [dict get $v::code($file) result clibraries]]
}
proc ::critcl::FixLibraries {libraries} {
if {[string match "win32-*-cl" $v::buildplatform]} {
# On windows using the native MSVC compiler, transform all
# -lFOO references into FOO.lib.
regsub -all -- {-l(\S+)} $libraries {\1.lib} libraries
} else {
# On unix we look for '-l:' references and rewrite them to the
# full path of the library, doing the search on our own.
#
# GNU ld understands this since at least 2.22 (don't know if
# earlier, 2.15 definitely doesn't), and it helps in
# specifying static libraries (Regular -l prefers .so over .a,
# and -l: overrides that).
# Search paths specified via -L, -libdir.
set lpath [SystemLibraryPaths]
set tmp {}
foreach word $libraries {
# Extend search path with -L options from clibraries.
if {[string match -L* $word]} {
lappend lpath [string range $word 2 end]
lappend tmp $word
continue
}
if {![string match -l:* $word]} {
lappend tmp $word
continue
}
# Search named library.
lappend tmp [ResolveColonSpec $lpath [string range $word 3 end]]
}
set libraries $tmp
}
return $libraries
}
proc ::critcl::ResolveColonSpec {lpath name} {
foreach path $lpath {
set f [file join $lpath $name]
if {![file exists $f]} continue
return $f
}
return -l:$name
}
proc ::critcl::SetupTkStubs {fd} {
puts -nonewline $fd [Cat [Template tkstubs.c]]
return
}
proc ::critcl::BuildDefines {fd file} {
# we process the cdefines in three steps
# - get the list of defines by preprocessing the source using the
# cpp -dM directive which causes any #defines to be output
# - extract the list of enums using regular expressions (not perfect,
# but will do for now)
# - generate Tcl_ObjSetVar2 commands to initialise Tcl variables
# Pull the collected ccode blocks together into a transient file
# we then search in.
set def [WriteCache define_[pid].c {}]
foreach digest [dict get $v::code($file) config defs] {
Append $def [dict get $v::code($file) config block $digest]
}
# For the command lines to be constructed we need all the include
# information the regular files will get during their compilation.
set hdrs [SystemIncludes $file]
# The result of the next two steps, a list of triples (namespace +
# label + value) of the defines to export.
set defines {}
# First step - get list of matching defines
set cmd [getconfigvalue preproc_define]
lappendlist cmd $hdrs
lappend cmd $def
set pipe [open "| $cmd" r]
while {[gets $pipe line] >= 0} {
# Check if the line contains a define.
set fields [split [string trim $line]]
if {[lindex $fields 0] ne "#define"} continue
# Yes. Get name and value. The latter is the joining of all
# fields after the name, except for any enclosing parentheses,
# which we strip off.
set var [lindex $fields 1]
set val [string trim [join [lrange $fields 2 end]] {()}]
# We ignore however any and all defines the user is not
# interested in making public. This is, in essence, a set
# intersection on the names of the defines.
if {![TakeDefine $file $var namespace]} continue
# And for those which are kept we integrate the information
# from both sources, i.e. namespace, and definition, under a
# single name.
lappend defines $namespace $var $val
}
close $pipe
# Second step - get list of enums
set cmd [getconfigvalue preproc_enum]
lappendlist cmd $hdrs
lappend cmd $def
set pipe [open "| $cmd" r]
set code [read $pipe]
close $pipe
set matches [regexp -all -inline {enum [^\{\(\)]*{([^\}]*)}} $code]
foreach {match submatch} $matches {
foreach line [split $submatch \n] {
foreach sub [split $line ,] {
set enum [lindex [split [string trim $sub]] 0]
# We ignore however any and all enum values the user
# is not interested in making public. This is, in
# essence, a set intersection on the names of the
# enum values.
if {![TakeDefine $file $enum namespace]} continue
# And for those which are kept we integrate the
# information from both sources, i.e. namespace, and
# definition, under a single name.
lappend defines $namespace $enum $enum
}
}
}
# Third step - generate Tcl_ObjSetVar2 commands exporting the
# defines and their values as Tcl variables.
foreach {namespace constname constvalue} $defines {
if {![info exists created($namespace)]} {
# we need to force the creation of the namespace
# because this code will be run before the user code
puts $fd " Tcl_Eval(ip, \"namespace eval $namespace {}\");"
set created($namespace) 1
}
set var "Tcl_NewStringObj(\"${namespace}::$constname\", -1)"
if {$constname eq $constvalue} {
# enum - assume integer
set constvalue "Tcl_NewIntObj($constvalue)"
} else {
# text or int - force to string
set constvalue "Tcl_NewStringObj(\"$constvalue\", -1)"
}
puts $fd " Tcl_ObjSetVar2(ip, $var, NULL, $constvalue, TCL_GLOBAL_ONLY);"
}
# Cleanup after ourselves, removing the helper file.
if {!$v::options(keepsrc)} { file delete $def }
return
}
proc ::critcl::TakeDefine {file identifier nsvar} {
upvar 1 $nsvar dst
if 0 {if {[dict exists $v::code($file) config const $identifier]} {
set dst [dict get $v::code($file) config const $identifier]
return 1
}}
foreach {pattern def} [dict get $v::code($file) config const] {
if {[string match $pattern $identifier]} {
set dst $def
return 1
}
}
return 0
}
proc ::critcl::Load {f} {
set shlib [dict get $v::code($f) result shlib]
set init [dict get $v::code($f) result initname]
set tsrc [dict get $v::code($f) result tsources]
set minv [dict get $v::code($f) result mintcl]
# Using the renamed builtin. While this is a dependency it was
# recorded already. See 'critcl::tcl', and 'critcl::tk'.
#package require Tcl $minv
::load $shlib $init
# See the critcl application for equivalent code placing the
# companion tcl sources into the generated package. Here, for
# 'compile & run' we now source the companion files directly.
foreach t $tsrc {
Ignore $t
source $t
}
return
}
proc ::critcl::AbortWhenCalledAfterBuild {} {
if {![done]} return
set cloc {}
if {![catch {
array set loc [info frame -2]
} msg]} {
if {$loc(type) eq "source"} {
set cloc "@$loc(file):$loc(line)"
} else {
set cloc " ([array get loc])"
}
} ;#else { set cloc " ($msg)" }
error "[lindex [info level -1] 0]$cloc: Illegal attempt to define C code in [This] after it was built."
}
# XXX Refactor to avoid duplication of the memoization code.
proc ::critcl::DetermineShlibName {file} {
# Return cached information, if present.
if {[info exists v::code($file)] &&
[dict exists $v::code($file) result shlib]} {
return [dict get $v::code($file) result shlib]
}
# The name of the shared library we hope to produce (or use)
set shlib [BaseOf $file][getconfigvalue sharedlibext]
dict set v::code($file) result shlib $shlib
return $shlib
}
proc ::critcl::DetermineObjectName {file} {
# Return cached information, if present.
if {[info exists v::code($file)] &&
[dict exists $v::code($file) result object]} {
return [dict get $v::code($file) result object]
}
set object [BaseOf $file]
# The generated object file will be saved for permanent use if the
# outdir option is set (in which case rebuilds will no longer be
# automatic).
if {$v::options(outdir) ne ""} {
set odir [file join [file dirname $file] $v::options(outdir)]
set oroot [file rootname [file tail $file]]
set object [file normalize [file join $odir $oroot]]
file mkdir $odir
}
# Modify the output file name if debugging symbols are requested.
if {$option::debug_symbols} {
append object _g
}
# Choose a distinct suffix so switching between them causes a
# rebuild.
switch -- $v::options(combine) {
"" -
dynamic { append object _pic[getconfigvalue object] }
static { append object _stub[getconfigvalue object] }
standalone { append object [getconfigvalue object] }
}
dict set v::code($file) result object $object
return $object
}
proc ::critcl::DetermineInitName {file prefix} {
set ininame [PkgInit $file]
# Add in the build prefix, if specified. This is done in mode
# 'generate package', for the pieces, ensuring that the overall
# initialization function cannot be in conflict with the
# initialization functions of these same pieces.
if {$prefix ne ""} {
set ininame "${prefix}_$ininame"
}
dict set v::code($file) result initname $ininame
catch {
dict set v::code($file) result pkgname \
[dict get $v::code($file) config package name]
}
return $ininame
}
proc ::critcl::PkgInit {file} {
# The init function name takes a capitalized prefix from the name
# of the input file name (alphanumeric prefix, including
# underscores). This implicitly drops the file extension, as the
# '.' is not an allowed character.
# While related to the package name, it can be different,
# especially if the package name contains :: separators.
if {$file eq {}} {
return Stdin
} else {
set ininame [file rootname [file tail $file]]
regsub -all {[^[:alnum:]_]} $ininame {} ininame
return [string totitle $ininame]
}
}
# # ## ### ##### ######## ############# #####################
## Implementation -- Internals - Access to the log file
proc ::critcl::LogOpen {file} {
file mkdir $v::cache
set v::logfile [file join $v::cache [pid].log]
set v::log [open $v::logfile w]
puts $v::log "\n[clock format [clock seconds]] - $file"
return
}
proc ::critcl::LogCmdline {cmdline} {
set w [join [lassign $cmdline cmd] \n\t]
Log \n$cmd\n\t$w\n
return
}
proc ::critcl::Log {msg} {
puts $v::log $msg
return
}
proc ::critcl::Log* {msg} {
puts -nonewline $v::log $msg
return
}
proc ::critcl::LogClose {} {
# Transfer the log messages for the current file over into the
# global critcl log, and cleanup.
close $v::log
set msgs [Cat $v::logfile]
AppendCache $v::prefix.log $msgs
file delete -force $v::logfile
unset v::log v::logfile
return $msgs
}
# # ## ### ##### ######## ############# #####################
## Implementation -- Internals - UUID management, change detection
proc ::critcl::UUID.extend {file key value} {
set digest [md5_hex /$value]
InitializeFile $file
dict update v::code($file) config c {
dict lappend c uuid $key $digest
}
return $digest
}
proc ::critcl::UUID.serial {file} {
InitializeFile $file
if {[catch {
set len [llength [dict get $v::code($file) config uuid]]
}]} {
set len 0
}
return $len
}
proc ::critcl::UUID {f} {
return [md5_hex "$f [GetParam $f uuid]"]
}
proc ::critcl::BaseOf {f} {
# Return cached information, if present.
if {[info exists v::code($f)] &&
[dict exists $v::code($f) result base]} {
return [dict get $v::code($f) result base]
}
set base [file normalize \
[file join $v::cache ${v::prefix}_[UUID $f]]]
dict set v::code($f) result base $base
return $base
}
# # ## ### ##### ######## ############# #####################
## Implementation -- Internals - Miscellanea
proc ::critcl::Separator {} {
return "/* [string repeat - 70] */"
}
proc ::critcl::Template {file} {
variable v::hdrdir
return [file join $hdrdir $file]
}
proc ::critcl::Copy {src dst} {
foreach p [glob -nocomplain $src] {
if {[file isdirectory $p]} {
set stem [file tail $p]
file mkdir $dst/$stem
Copy $p/* $dst/$stem
} else {
file copy -force $p $dst
}
}
}
proc ::critcl::Cat {path} {
# Easier to write our own copy than requiring fileutil and then
# using fileutil::cat.
set fd [open $path r]
set data [read $fd]
close $fd
return $data
}
proc ::critcl::WriteCache {name content} {
set dst [file join $v::cache $name]
file mkdir [file dirname $dst] ;# just in case
return [Write [file normalize $dst] $content]
}
proc ::critcl::Write {path content} {
set chan [open $path w]
puts $chan $content
close $chan
return $path
}
proc ::critcl::AppendCache {name content} {
file mkdir $v::cache ;# just in case
return [Append [file normalize [file join $v::cache $name]] $content]
}
proc ::critcl::Append {path content} {
set chan [open $path a]
puts $chan $content
close $chan
return $path
}
# # ## ### ##### ######## ############# #####################
## Implementation -- Internals - Status Operations, and execution
## of external commands.
proc ::critcl::StatusReset {} {
set v::failed 0
return
}
proc ::critcl::StatusAbort? {} {
if {$v::failed} { return -code return }
return
}
proc ::critcl::StatusSave {file} {
# XXX FUTURE Use '$(file) result failed' later
set result $v::failed
set v::code($file,failed) $v::failed
set v::failed 0
return $result
}
proc ::critcl::CheckForWarnings {text} {
set warnings [dict create]
foreach line [split $text \n] {
# Ignore everything not a warning.
if {![string match -nocase *warning* $line]} continue
# Ignore duplicates (which is why we store the lines as dict
# keys for now).
if {[dict exists $warnings $line]} continue
dict set warnings $line .
}
return [dict keys $warnings]
}
proc ::critcl::Exec {cmdline} {
variable run
set v::failed [catch {
interp eval $run [linsert $cmdline 0 exec]
} v::err]
return [expr {!$v::failed}]
}
proc ::critcl::ExecWithLogging {cmdline okmsg errmsg} {
variable run
LogCmdline $cmdline
# Extend the command, redirect all of its output (stdout and
# stderr) into the current log.
lappend cmdline >&@ $v::log
interp transfer {} $v::log $run
set ok [Exec $cmdline]
interp transfer $run $v::log {}
if {$ok} {
Log [uplevel 1 [list subst $okmsg]]
} else {
Log [uplevel 1 [list subst $errmsg]]
Log $v::err
}
return $ok
}
proc ::critcl::BuildPlatform {} {
set platform [::platform::generic]
# Behave like a autoconf generated configure
# - $CC (user's choice first)
# - gcc, if available.
# - cc/cl otherwise (without further check for availability)
if {[info exists ::env(CC)]} {
# The compiler may be a gcc, despite being named .../cc.
set cc $::env(CC)
if {[IsGCC $cc]} {
set cc gcc
}
} elseif {[llength [auto_execok gcc]]} {
set cc gcc
} else {
if {[string match "win32-*" $platform]} {
set cc cl
} else {
set cc cc
}
}
# The cc may be a full path, through the CC environment variable,
# which is bad for use in the platform code. Use only the last
# element of said path, without extensions (.exe). And it may be
# followed by options too, so look for and strip these off as
# well. This last part assumes that the path of the compiler
# itself doesn't contain spaces.
regsub {( .*)$} [file tail $cc] {} cc
append platform -[file rootname $cc]
# Memoize
proc ::critcl::BuildPlatform {} [list return $platform]
return $platform
}
proc ::critcl::IsGCC {path} {
if {[catch {
set lines [exec $path -v |& grep gcc]
}] || ($lines eq {})} { return 0 }
return 1
}
proc ::critcl::This {} {
variable v::this
# For management of v::this see critcl::{source,collect*}
# If present, an output redirection is active.
if {[info exists this] && [llength $this]} {
return [lindex $this end]
}
return [file normalize [info script]]
}
proc ::critcl::Here {} {
return [file dirname [This]]
}
proc ::critcl::TclDecls {file} {
return [TclDef $file tclDecls.h tclStubsPtr]
}
proc ::critcl::TclPlatDecls {file} {
return [TclDef $file tclPlatDecls.h tclPlatStubsPtr]
}
proc ::critcl::TclDef {file hdr var} {
#puts F|$file
set hdr [TclHeader $file $hdr]
if {![file exists $hdr]} { error "Header file not found: $hdr" }
if {![file isfile $hdr]} { error "Header not a file: $hdr" }
if {![file readable $hdr]} { error "Header not readable: $hdr (no permission)" }
#puts H|$hdr
if {[catch {
set hdrcontent [split [Cat $hdr] \n]
} msg]} {
error "Header not readable: $hdr ($msg)"
}
# Note, Danger: The code below is able to use declarations which
# are commented out in various ways (#if 0, /* ... */, and //
# ...), because it is performing a simple line-oriented search
# without context, and not matching against comment syntax either.
set ext [Grep *extern* $hdrcontent]
if {![llength $ext]} {
error "No extern declarations found in $hdr"
}
set vardecl [Grep *${var}* $ext]
if {![llength $vardecl]} {
error "No declarations for $var found in $hdr"
}
set def [string map {extern {}} [lindex $vardecl 0]]
msg " ($var => $def)"
return $def
}
proc ::critcl::Grep {pattern lines} {
set r {}
foreach line $lines {
if {![string match $pattern $line]} continue
lappend r $line
}
return $r
}
# # ## ### ##### ######## ############# #####################
## Initialization
proc ::critcl::Initialize {} {
variable mydir [Here] ; # Path of the critcl package directory.
variable run [interp create]
variable v::buildplatform [BuildPlatform]
variable v::hdrdir [file join $mydir critcl_c]
variable v::hdrsavailable
# Scan the directory holding the C fragments and our copies of the
# Tcl header and determine for which versions of Tcl we actually
# have headers. This allows distributions to modify the directory,
# i.e. drop our copies and refer to the system headers instead, as
# much as are installed, and critcl adapts. The tcl versions are
# recorded in ascending order, making upcoming searches easier,
# the first satisfying version is also always the smallest.
foreach d [lsort -dict [glob -types {d r} -directory $hdrdir -tails tcl*]] {
lappend hdrsavailable [regsub {^tcl} $d {}]
}
# The prefix is based on the package's version. This allows
# multiple versions of the package to use the same cache without
# interfering with each. Note that we cannot use 'pid' and similar
# information, because this would circumvent the goal of the
# cache, the reuse of binaries whose sources did not change.
variable v::prefix "v[package require critcl]"
regsub -all {\.} $prefix {} prefix
# keep config options in a namespace
foreach var $v::configvars {
set c::$var {}
}
# read default configuration. This also chooses and sets the
# target platform.
readconfig [file join $mydir Config]
# Declare the standard argument types for cproc.
argtype int {
if (Tcl_GetIntFromObj(interp, @@, &@A) != TCL_OK) return TCL_ERROR;
}
argtype boolean {
if (Tcl_GetBooleanFromObj(interp, @@, &@A) != TCL_OK) return TCL_ERROR;
} int int
argtype bool = boolean
argtype long {
if (Tcl_GetLongFromObj(interp, @@, &@A) != TCL_OK) return TCL_ERROR;
}
argtype double {
if (Tcl_GetDoubleFromObj(interp, @@, &@A) != TCL_OK) return TCL_ERROR;
}
argtype float {
double t;
if (Tcl_GetDoubleFromObj(interp, @@, &t) != TCL_OK) return TCL_ERROR;
@A = (float) t;
}
argtype char* {
@A = Tcl_GetString(@@);
}
argtype pstring {
@A.s = Tcl_GetStringFromObj(@@, &(@A.len));
} critcl_pstring critcl_pstring
argtypesupport pstring {
typedef struct critcl_pstring {
char* s;
int len;
} critcl_pstring;
}
argtype Tcl_Obj* {
@A = @@;
}
argtype object = Tcl_Obj*
## The next set of argument types looks to be very broken. We are
## keeping them for now, but declare them as DEPRECATED. Their
## documentation will be removed in version 3.2, and their
## implementation in 3.3 as well, fully exterminating them
argtype int* {
/* Raw pointer in binary Tcl value */
@A = (int*) Tcl_GetByteArrayFromObj(@@, NULL);
Tcl_InvalidateStringRep(@@);
}
argtype float* {
/* Raw pointer in binary Tcl value */
@A = (float*) Tcl_GetByteArrayFromObj(@@, NULL);
}
argtype double* {
/* Raw pointer in binary Tcl value */
@A = (double*) Tcl_GetByteArrayFromObj(@@, NULL);
}
argtype bytearray {
/* Raw binary string. Length information is _NOT_ propagated */
@A = (char*) Tcl_GetByteArrayFromObj(@@, NULL);
Tcl_InvalidateStringRep(@@);
} char* char*
argtype rawchar = bytearray
argtype rawchar* = bytearray
# Declare the standard result types for cproc.
# System still has special case code for:
# - void (no rv result variable).
resulttype void {
return TCL_OK;
}
resulttype ok {
return rv;
} int
resulttype int {
Tcl_SetObjResult(interp, Tcl_NewIntObj(rv));
return TCL_OK;
}
resulttype boolean = int
resulttype bool = int
resulttype long {
Tcl_SetObjResult(interp, Tcl_NewLongObj(rv));
return TCL_OK;
}
resulttype double {
Tcl_SetObjResult(interp, Tcl_NewDoubleObj(rv));
return TCL_OK;
}
resulttype float {
Tcl_SetObjResult(interp, Tcl_NewDoubleObj(rv));
return TCL_OK;
}
# Static and volatile strings. Duplicate.
resulttype char* {
Tcl_SetObjResult(interp, Tcl_NewStringObj(rv,-1));
return TCL_OK;
}
resulttype {const char*} {
Tcl_SetObjResult(interp, Tcl_NewStringObj(rv,-1));
return TCL_OK;
}
resulttype vstring = char*
# Dynamic strings, allocated via Tcl_Alloc.
#
# We are avoiding the Tcl_Obj* API here, as its use requires an
# additional duplicate of the string, churning memory and
# requiring more copying.
# Tcl_SetObjResult(interp, Tcl_NewStringObj(rv,-1));
# Tcl_Free (rv);
resulttype string {
Tcl_SetResult (interp, rv, TCL_DYNAMIC);
return TCL_OK;
} char*
resulttype dstring = string
resulttype Tcl_Obj* {
if (rv == NULL) { return TCL_ERROR; }
Tcl_SetObjResult(interp, rv);
Tcl_DecrRefCount(rv);
return TCL_OK;
}
resulttype object = Tcl_Obj*
rename ::critcl::Initialize {}
return
}
# # ## ### ##### ######## ############# #####################
## State
namespace eval ::critcl {
variable mydir ;# Path of the critcl package directory.
variable run ;# interpreter to run commands, eval when, etc
# XXX configfile - See the *config commands, path of last config file run through 'readconfig'.
# namespace to flag when options set
namespace eval option {
variable debug_symbols 0
}
# keep all variables in a sub-namespace for easy access
namespace eval v {
variable cache ;# Path. Cache directory. Platform-dependent
# (target platform).
# ----------------------------------------------------------------
# (XX) To understand the set of variables below and their
# differences some terminology is required.
#
# First we have to distinguish between "target identifiers"
# and "platform identifiers". The first is the name for a
# particular set of configuration settings specifying commands
# and command line arguments to use. The second is the name of
# a machine configuration, identifying both operating system,
# and cpu architecture.
#
# The problem critcl has is that in 99% of the cases found in
# a critcl config file the "target identifier" is also a valid
# "platform identifier". Example: "linux-ix86". That does not
# make them semantically interchangable however.
#
# Especially when we add cross-compilation to the mix, where
# we have to further distinguish between the platform critcl
# itself is running on (build), and the platform for which
# critcl is generating code (target), and the last one sounds
# similar to "target identifier".
variable targetconfig ;# Target identifier. The chosen configuration.
variable targetplatform ;# Platform identifier. We generate binaries for there.
variable buildplatform ;# Platform identifier. We run here.
variable knowntargets {} ;# List of all target identifiers found
# in the configuration file last processed by "readconfig".
variable xtargets ;# Cross-compile targets. This array maps from
array set xtargets {} ;# the target identifier to the actual platform
# identifier of the target platform in question. If a target identifier
# has no entry here, it is assumed to be the platform identifier itself.
# See "critcl::actualtarget".
# ----------------------------------------------------------------
variable version "" ;# String. Min version number on platform
variable hdrdir ;# Path. Directory containing the helper
# files of the package. A sub-
# directory of 'mydir', see above.
variable hdrsavailable ;# List. Of Tcl versions for which we have
# Tcl header files available. For details
# see procedure 'Initialize' above.
variable prefix ;# String. The string to start all file names
# generated by the package with. See
# 'Initialize' for our choice and
# explanation of it.
variable options ;# An array containing options
# controlling the code generator.
# For more details see below.
set options(outdir) "" ;# - Path. If set the place where the generated
# shared library is saved for permanent use.
set options(keepsrc) 0 ;# - Boolean. If set all generated .c files are
# kept after compilation. Helps with debugging
# the critcl package.
set options(combine) "" ;# - XXX standalone/dynamic/static
# XXX Meaning of combine?
set options(force) 0 ;# - Boolean. If set (re)compilation is
# forced, regardless of the state of
# the cache.
set options(I) "" ;# - List. Additional include
# directories, globally specified by
# the user for mode 'generate
# package', for all components put
# into the package's library.
set options(L) "" ;# - List. Additional library search
# directories, globally specified by
# the user for mode 'generate
# package'.
set options(language) "" ;# - String. XXX
set options(lines) 1 ;# - Boolean. If set the generator will
# emit #line-directives to help locating
# C code in the .tcl in case of compile
# warnings and errors.
# XXX clientdata() per-command (See ccommand). per-file+ccommand better?
# XXX delproc() per-command (See ccommand). s.a
# XXX toolchain() <platform>,<configvarname> -> data
# XXX Used only in {read,set,show}config.
# XXX Seems to be a database holding the total contents of the
# XXX config file.
# knowntargets - See the *config commands, list of all platforms we can compile for.
# I suspect that this came later
# Conversion maps, Tcl types for procedure arguments and
# results to C types and code fragments for the conversion
# between the realms. Used by the helper commands
# "ArgumentCType", "ArgumentConversion", and
# "ResultConversion". These commands also supply the default
# values for unknown types.
variable actype
array set actype {}
variable actypeb
array set actypeb {}
# In the code fragments below we have the following environment (placeholders, variables):
# ip - C variable, Tcl_Interp* of the interpreter providing the arguments.
# @@ - Tcl_Obj* valued expression returning the Tcl argument value.
# @A - Name of the C-level argument variable.
#
variable aconv
array set aconv {}
# Mapping from cproc result to C result type of the function.
# This is also the C type of the helper variable holding the result.
# NOTE: 'void' is special, as it has no result, nor result variable.
variable rctype
array set rctype {}
# In the code fragments for result conversion:
# 'rv' == variable capturing the return value of the C function.
# 'ip' == variable containing pointer to the interp to set the result into.
variable rconv
array set rconv {}
variable storageclass {
/*
* These macros are used to control whether functions are being declared for
* import or export. If a function is being declared while it is being built
* to be included in a shared library, then it should have the DLLEXPORT
* storage class. If is being declared for use by a module that is going to
* link against the shared library, then it should have the DLLIMPORT storage
* class. If the symbol is beind declared for a static build or for use from a
* stub library, then the storage class should be empty.
*
* The convention is that a macro called BUILD_xxxx, where xxxx is the name of
* a library we are building, is set on the compile line for sources that are
* to be placed in the library. When this macro is set, the storage class will
* be set to DLLEXPORT. At the end of the header file, the storage class will
* be reset to DLLIMPORT.
*/
#undef TCL_STORAGE_CLASS
#ifdef BUILD_@cname@
# define TCL_STORAGE_CLASS DLLEXPORT
#else
# ifdef USE_@up@_STUBS
# define TCL_STORAGE_CLASS
# else
# define TCL_STORAGE_CLASS DLLIMPORT
# endif
#endif
}
variable code ;# This array collects all code snippets and
# data about them.
# Keys for 'code' (above) and their contents:
#
# <file> -> Per-file information, nested dictionary. Sub keys:
#
# result - Results needed for 'generate package'.
# initname - String. Foo in Foo_Init().
# tsources - List. The companion tcl sources for <file>.
# object - String. Name of the object file backing <file>.
# objects - List. All object files, main and companions.
# shlib - String. Name of the shared library backing <file>.
# base - String. Common prefix (file root) of 'object' and 'shlib'.
# clibraries - List. See config. Copy for global linkage.
# ldflags - List. See config. Copy for global linkage.
# mintcl - String. Minimum version of Tcl required by the package.
# preload - List. Names of all libraries to load before the package library.
# license - String. License text.
# <= "critcl::cresults"
#
# config - Collected code and configuration (ccode, etc.).
# tsources - List. The companion tcl sources for <file>.
# => "critcl::tsources".
# cheaders - List. => "critcl::cheaders"
# csources - List. => "critcl::csources"
# clibraries - List. => "critcl::clibraries"
# cflags - List. => "critcl::cflags", "critcl::framework",
# "critcl::debug", "critcl::include"
# ldflags - List. => "critcl::ldflags", "critcl::framework"
# initc - String. Initialization code for Foo_Init(), "critcl::cinit"
# edecls - String. Declarations of externals needed by Foo_Init(), "critcl::cinit"
# functions - List. Collected function names.
# fragments - List. Hashes of the collected C source bodies (functions, and unnamed code).
# block - Dictionary. Maps the hashes to their C sources for fragments.
# defs - List. Hashes of the collected C source bodies (only unnamed code), for extraction of defines.
# const - Dictionary. Maps the names of defines to the namespace their variables will be in.
# uuid - List. Strings used to generate the file's uuid/hash.
# mintcl - String. Minimum version of Tcl required by the package.
# preload - List. Names of all libraries to load
# before the package library. This
# information is used only by mode
# 'generate package'. This means that
# packages with preload can't be used
# in mode 'compile & run'.
# license - String. License text.
# api_self - String. Name of our API. Defaults to package name.
# api_hdrs - List. Exported public headers of the API.
# api_ehdrs - List. Exported external public headers of the API.
# api_fun - List. Exported functions (signatures of result type, name, and arguments (C syntax))
# meta - Dictionary. Arbitrary keys to values, the user meta-data for the package.
# package - Dictionary. Keys, see below. System meta data for the package. Values are lists.
# name - Name of current package
# version - Version of same.
# description - Long description.
# summary - Short description (one line).
# subject - Keywords and -phrases.
# as::build::date - Date-stamp for the build.
#
# ---------------------------------------------------------------------
#
# <file>,failed -> Per-file information: Boolean. Build status. Failed or not.
#
# 'ccode' -> Accumulated in-memory storage of code-fragments.
# Extended by 'ccode', used by 'BuildDefines',
# called by 'cbuild'. Apparently tries to extract defines
# and enums, and their values, for comparison with 'cdefine'd
# values.
#
# NOTE: <file> are normalized absolute path names for exact
# identification of the relevant .tcl file.
# _____________________________________________________________________
# State used by "cbuild" ______________________________________________
variable log "" ;# Log channel, opened to logfile.
variable logfile "" ;# Path of logfile. Accessed by
# "Log*" and "ExecWithLogging".
variable failed 0 ;# Build status. Used by "Status*"
variable err "" ;# and "Exec*". Build error text.
variable buildforpackage 0 ;# Boolean flag controlling
# cbuild's behaviour. Named after
# the mode 'generate package'.
# Auto-resets to OFF after each
# call of "cbuild". Can be activated
# by "buildforpackage".
# _____________________________________________________________________
# State used by "BeginCommand", "EndCommand", "Emit*" _________________
variable curr ;# Hash of the last BeginCommand.
variable block ;# C code assembled by Emit* calls
# between Begin- and EndCommand.
# _____________________________________________________________________
variable compiling 0 ;# Boolean. Indicates that a C compiler
# (gcc, native, cl) is available.
# _____________________________________________________________________
# config variables
variable configvars {
compile
debug_memory
debug_symbols
include
libinclude
ldoutput
embed_manifest
link
link_debug
link_preload
link_release
noassert
object
optimize
output
platform
preproc_define
preproc_enum
sharedlibext
strip
tclstubs
threadflags
tkstubs
version
}
}
# namespace holding the compiler configuration (commands and
# options for the various tasks, i.e. compilation, linking, etc.).
namespace eval c {
# See sibling file 'Config' for the detailed and full
# information about the variables in use. configvars above, and
# the code below list only the variables relevant to C. Keep this
# information in sync with the contents of 'Config'.
# compile Command to compile a C source file to an object file
# debug_memory Compiler flags to enable memory debugging
# debug_symbols Compiler flags to add symbols to resulting library
# include Compiler flag to add an include directory
# libinclude Linker flag to add a library directory
# ldoutput - ? See 'Config'
# link Command to link one or more object files and create a shared library
# embed_manifest Command to embed a manifest into a DLL. (Win-specific)
# link_debug - ? See 'Config'
# link_preload Linker flags to use when dependent libraries are pre-loaded.
# link_release - ? See 'Config'
# noassert Compiler flag to turn off assertions in Tcl code
# object File extension for object files
# optimize Compiler flag to specify optimization level
# output Compiler flag to set output file, with argument $object => Use via [subst].
# platform Platform identification string (defaults to platform::generic)
# preproc_define Command to preprocess C source file (for critcl::cdefines)
# preproc_enum ditto
# sharedlibext The platform's file extension used for shared library files.
# strip Compiler flag to tell the linker to strip symbols
# target Presence of this key indicates that this is a cross-compile target
# tclstubs Compiler flag to set USE_TCL_STUBS
# threadflags Compiler flags to enable threaded build
# tkstubs Compiler flag to set USE_TK_STUBS
# version Command to print the compiler version number
}
}
# # ## ### ##### ######## ############# #####################
## Export API
namespace eval ::critcl {
namespace export \
at cache ccode ccommand cdata cdefines cflags cheaders \
check cinit clibraries compiled compiling config cproc \
csources debug done failed framework ldflags platform \
tk tsources preload license load tcl api userconfig meta
# This is exported for critcl::app to pick up when generating the
# dummy commands in the runtime support of a generated package.
namespace export Ignore
catch { namespace ensemble create }
}
# # ## ### ##### ######## ############# #####################
## Ready
::critcl::Initialize
return
|