Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Comment: | Update for TIP-551:
Add documentation for this feature to the expr man page. The keyword "integer value" has been added to the string and expr man page. Added TCL_PARSE_NO_UNDERSCORE flag so that the digit separator can be disabled when need when calling TclParseNumber. Disabled digit separator in the "scan" command when scanning integers and floating-point numbers. This is the one place where existing code may rely on number parsing to stop at an underscore. Disallow underscore between the leading 0 and the radix specifiers 'x', 'o', 'b', and 'd'. Added tests for disallowed underscore use and scan with underscores between digits in the source string. |
---|---|
Downloads: | Tarball | ZIP archive |
Timelines: | family | ancestors | descendants | both | tip-551 |
Files: | files | file ages | folders |
SHA3-256: |
002a6fdc7de103974830b1dc9ee0f02b |
User & Date: | griffin 2020-05-23 03:32:30.308 |
Original Comment: | generic/comment.txt |
2020-05-30
| ||
23:47 | tip-551 implementation. check-in: 3785bbf5a3 user: griffin tags: core-8-branch | |
2020-05-23
| ||
03:32 |
Update for TIP-551:
Add documentation for this feature to the expr man page. The keyword "integ... Closed-Leaf check-in: 002a6fdc7d user: griffin tags: tip-551 | |
2019-12-07
| ||
05:52 | Initial implementation for TIP-551 Permit underscores in numeric literals check-in: 64b80d4ecd user: griffin tags: tip-551 | |
︙ | ︙ | |||
13 14 15 16 17 18 19 | .SH NAME expr \- Evaluate an expression .SH SYNOPSIS \fBexpr \fIarg \fR?\fIarg arg ...\fR? .BE .SH DESCRIPTION .PP | | | 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | .SH NAME expr \- Evaluate an expression .SH SYNOPSIS \fBexpr \fIarg \fR?\fIarg arg ...\fR? .BE .SH DESCRIPTION .PP The \fIexpr\fR command concatenates \fIarg\fRs, separated by a space, into an expression, and evaluates that expression, returning its value. The operators permitted in an expression include a subset of the operators permitted in C expressions. For those operators common to both Tcl and C, Tcl applies the same meaning and precedence as the corresponding C operators. The value of an expression is often a numeric result, either an integer or a floating-point value, but may also be a non-numeric value. |
︙ | ︙ | |||
42 43 44 45 46 47 48 | value is the form produced by the \fB%g\fR format specifier of Tcl's \fBformat\fR command. .SS OPERANDS .PP An expression consists of a combination of operands, operators, parentheses and commas, possibly with whitespace between any of these elements, which is ignored. | < < < < < < < < < < < < < < < < | 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | value is the form produced by the \fB%g\fR format specifier of Tcl's \fBformat\fR command. .SS OPERANDS .PP An expression consists of a combination of operands, operators, parentheses and commas, possibly with whitespace between any of these elements, which is ignored. .PP An operand may be specified in any of the following ways: .IP [1] As a numeric value, either integer or floating-point. .IP [2] As a boolean value, using any form understood by \fBstring is\fR \fBboolean\fR. |
︙ | ︙ | |||
99 100 101 102 103 104 105 106 107 108 109 110 111 112 | .CS .ta 9c \fBexpr\fR 3.1 + $a \fI6.1\fR \fBexpr\fR 2 + "$a.$b" \fI5.6\fR \fBexpr\fR 4*[llength "6 2"] \fI8\fR \fBexpr\fR {{word one} < "word $a"} \fI0\fR .CE .SS OPERATORS .PP For operators having both a numeric mode and a string mode, the numeric mode is chosen when all operands have a numeric interpretation. The integer interpretation of an operand is preferred over the floating-point interpretation. To ensure string operations on arbitrary values it is generally a good idea to use \fBeq\fR, \fBne\fR, or the \fBstring\fR command instead of | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | .CS .ta 9c \fBexpr\fR 3.1 + $a \fI6.1\fR \fBexpr\fR 2 + "$a.$b" \fI5.6\fR \fBexpr\fR 4*[llength "6 2"] \fI8\fR \fBexpr\fR {{word one} < "word $a"} \fI0\fR .CE .PP \fBInteger value\fR .PP An integer operand may be specified in decimal (the normal case, the optional first two characters are \fB0d\fR), binary (the first two characters are \fB0b\fR), octal (the first two characters are \fB0o\fR), or hexadecimal (the first two characters are \fB0x\fR) form. For compatibility with older Tcl releases, an operand that begins with \fB0\fR is interpreted as an octal integer even if the second character is not \fBo\fR. .PP \fBFloating-point value\fR .PP A floating-point number may be specified in any of several common decimal formats, and may use the decimal point \fB.\fR, \fBe\fR or \fBE\fR for scientific notation, and the sign characters \fB+\fR and \fB\-\fR. The following are all valid floating-point numbers: 2.1, 3., 6e4, 7.91e+16. The strings \fBInf\fR and \fBNaN\fR, in any combination of case, are also recognized as floating point values. An operand that doesn't have a numeric interpretation must be quoted with either braces or with double quotes. .PP \fBBoolean value\fR .PP A boolean value may be represented by any of the values \fB0\fR, \fBfalse\fR, \fBno\fR, or \fBoff\fR and any of the values \fB1\fR, \fBtrue\fR, \fByes\fR, or \fBon\fR. .PP \fBDigit Separator\fR .PP Digits in any numeric value may be separated with one or more underscore characters, "\fB_\fR", to improve readability. These separators may only appear between digits. The separator may not appear at the start of a numeric value, between the leading 0 and radix specifier, or at the end of a numeric value. Here are some examples: .PP .CS .ta 9c \fBexpr\fR 100_000_000 \fI100000000\fR \fBexpr\fR 0xffff_ffff \fI4294967295\fR \fBformat\fR 0x%x 0b1111_1110_1101_1011 \fI0xfedb\fR .CE .PP .SS OPERATORS .PP For operators having both a numeric mode and a string mode, the numeric mode is chosen when all operands have a numeric interpretation. The integer interpretation of an operand is preferred over the floating-point interpretation. To ensure string operations on arbitrary values it is generally a good idea to use \fBeq\fR, \fBne\fR, or the \fBstring\fR command instead of |
︙ | ︙ | |||
470 471 472 473 474 475 476 | .CS set randNum [\fBexpr\fR { int(100 * rand()) }] .CE .SH "SEE ALSO" array(n), for(n), if(n), mathfunc(n), mathop(n), namespace(n), proc(n), string(n), Tcl(n), while(n) .SH KEYWORDS | | | 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 | .CS set randNum [\fBexpr\fR { int(100 * rand()) }] .CE .SH "SEE ALSO" array(n), for(n), if(n), mathfunc(n), mathop(n), namespace(n), proc(n), string(n), Tcl(n), while(n) .SH KEYWORDS arithmetic, boolean, compare, expression, fuzzy comparison, integer value .SH COPYRIGHT .nf Copyright \(co 1993 The Regents of the University of California. Copyright \(co 1994-2000 Sun Microsystems Incorporated. Copyright \(co 2005 by Kevin B. Kenny <kennykb@acm.org>. All rights reserved. .fi '\" Local Variables: '\" mode: nroff '\" End: |
︙ | ︙ | |||
501 502 503 504 505 506 507 | } else { set isPrefix [\fBstring equal\fR \-length $length $string "foobar"] } .CE .SH "SEE ALSO" expr(n), list(n) .SH KEYWORDS | | | 501 502 503 504 505 506 507 508 509 510 511 512 | } else { set isPrefix [\fBstring equal\fR \-length $length $string "foobar"] } .CE .SH "SEE ALSO" expr(n), list(n) .SH KEYWORDS case conversion, compare, index, integer value, match, pattern, string, word, equal, ctype, character, reverse .\" Local Variables: .\" mode: nroff .\" End: |
︙ | ︙ | |||
2699 2700 2701 2702 2703 2704 2705 2706 2707 2708 2709 2710 2711 2712 | #define TCL_PARSE_SCAN_PREFIXES 16 /* Use [scan] rules dealing with 0? * prefixes. */ #define TCL_PARSE_NO_WHITESPACE 32 /* Reject leading/trailing whitespace. */ #define TCL_PARSE_BINARY_ONLY 64 /* Parse binary even without prefix. */ /* *---------------------------------------------------------------------- * Type values TclGetNumberFromObj *---------------------------------------------------------------------- */ | > > | 2699 2700 2701 2702 2703 2704 2705 2706 2707 2708 2709 2710 2711 2712 2713 2714 | #define TCL_PARSE_SCAN_PREFIXES 16 /* Use [scan] rules dealing with 0? * prefixes. */ #define TCL_PARSE_NO_WHITESPACE 32 /* Reject leading/trailing whitespace. */ #define TCL_PARSE_BINARY_ONLY 64 /* Parse binary even without prefix. */ #define TCL_PARSE_NO_UNDERSCORE 128 /* Reject underscore digit separator */ /* *---------------------------------------------------------------------- * Type values TclGetNumberFromObj *---------------------------------------------------------------------- */ |
︙ | ︙ |
︙ | ︙ | |||
898 899 900 901 902 903 904 | */ objPtr = Tcl_NewWideIntObj(0); Tcl_IncrRefCount(objPtr); if (width == 0) { width = ~0; } if (TCL_OK != TclParseNumber(NULL, objPtr, NULL, string, width, | | | 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 | */ objPtr = Tcl_NewWideIntObj(0); Tcl_IncrRefCount(objPtr); if (width == 0) { width = ~0; } if (TCL_OK != TclParseNumber(NULL, objPtr, NULL, string, width, &end, TCL_PARSE_INTEGER_ONLY | TCL_PARSE_NO_UNDERSCORE | parseFlag)) { Tcl_DecrRefCount(objPtr); if (width < 0) { if (*end == '\0') { underflow = 1; } } else { if (end == string + width) { |
︙ | ︙ | |||
1002 1003 1004 1005 1006 1007 1008 | objPtr = Tcl_NewDoubleObj(0.0); Tcl_IncrRefCount(objPtr); if (width == 0) { width = ~0; } if (TCL_OK != TclParseNumber(NULL, objPtr, NULL, string, width, | | | 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 | objPtr = Tcl_NewDoubleObj(0.0); Tcl_IncrRefCount(objPtr); if (width == 0) { width = ~0; } if (TCL_OK != TclParseNumber(NULL, objPtr, NULL, string, width, &end, TCL_PARSE_DECIMAL_ONLY | TCL_PARSE_NO_WHITESPACE | TCL_PARSE_NO_UNDERSCORE)) { Tcl_DecrRefCount(objPtr); if (width < 0) { if (*end == '\0') { underflow = 1; } } else { if (end == string + width) { |
︙ | ︙ |
︙ | ︙ | |||
635 636 637 638 639 640 641 | * OCTAL state differ only in whether they recognize 'X' and 'b'. */ acceptState = state; acceptPoint = p; acceptLen = len; if (c == 'x' || c == 'X') { | < | < | > > > < | > > | 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 | * OCTAL state differ only in whether they recognize 'X' and 'b'. */ acceptState = state; acceptPoint = p; acceptLen = len; if (c == 'x' || c == 'X') { if (flags & (TCL_PARSE_OCTAL_ONLY|TCL_PARSE_BINARY_ONLY) || under) { goto endgame; } state = ZERO_X; break; } if (flags & TCL_PARSE_HEXADECIMAL_ONLY) { goto zerox; } if (flags & TCL_PARSE_SCAN_PREFIXES) { goto zeroo; } if (c == 'b' || c == 'B') { if ((flags & TCL_PARSE_OCTAL_ONLY) || under) { goto endgame; } state = ZERO_B; break; } if (flags & TCL_PARSE_BINARY_ONLY) { goto zerob; } if (c == 'o' || c == 'O') { if (under) { goto endgame; } explicitOctal = 1; state = ZERO_O; break; } if (c == 'd' || c == 'D') { if (under) { goto endgame; } state = ZERO_D; break; } #ifdef TCL_NO_DEPRECATED goto decimal; #endif /* FALLTHROUGH */ |
︙ | ︙ | |||
737 738 739 740 741 742 743 | numSigDigs += numTrailZeros+1; } else { numSigDigs = 1; } numTrailZeros = 0; state = OCTAL; break; | | | 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 | numSigDigs += numTrailZeros+1; } else { numSigDigs = 1; } numTrailZeros = 0; state = OCTAL; break; } else if (c == '_' && !(flags & TCL_PARSE_NO_UNDERSCORE)) { /* Ignore numeric "white space" */ under = 1; break; } /* FALLTHROUGH */ case BAD_OCTAL: |
︙ | ︙ | |||
828 829 830 831 832 833 834 | d = (c-'0'); } else if (c >= 'A' && c <= 'F') { under = 0; d = (c-'A'+10); } else if (c >= 'a' && c <= 'f') { under = 0; d = (c-'a'+10); | | | 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 | d = (c-'0'); } else if (c >= 'A' && c <= 'F') { under = 0; d = (c-'A'+10); } else if (c >= 'a' && c <= 'f') { under = 0; d = (c-'a'+10); } else if (c == '_' && !(flags & TCL_PARSE_NO_UNDERSCORE)) { /* Ignore numeric "white space" */ under = 1; break; } else { goto endgame; } if (objPtr != NULL) { |
︙ | ︙ | |||
874 875 876 877 878 879 880 | case ZERO_B: zerob: if (c == '0') { numTrailZeros++; under = 0; state = BINARY; break; | | | 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 | case ZERO_B: zerob: if (c == '0') { numTrailZeros++; under = 0; state = BINARY; break; } else if (c == '_' && !(flags & TCL_PARSE_NO_UNDERSCORE)) { /* Ignore numeric "white space" */ under = 1; break; } else if (c != '1') { goto endgame; } if (objPtr != NULL) { |
︙ | ︙ | |||
914 915 916 917 918 919 920 | break; case ZERO_D: if (c == '0') { under = 0; numTrailZeros++; } else if ( ! isdigit(UCHAR(c))) { | | | 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 | break; case ZERO_D: if (c == '0') { under = 0; numTrailZeros++; } else if ( ! isdigit(UCHAR(c))) { if (c == '_' && !(flags & TCL_PARSE_NO_UNDERSCORE)) { /* Ignore numeric "white space" */ under = 1; break; } goto endgame; } under = 0; |
︙ | ︙ | |||
955 956 957 958 959 960 961 | significandOverflow); } numSigDigs += numTrailZeros+1; numTrailZeros = 0; under = 0; state = DECIMAL; break; | | | 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 | significandOverflow); } numSigDigs += numTrailZeros+1; numTrailZeros = 0; under = 0; state = DECIMAL; break; } else if (c == '_' && !(flags & TCL_PARSE_NO_UNDERSCORE)) { /* Ignore numeric "white space" */ under = 1; break; } else if (flags & TCL_PARSE_INTEGER_ONLY) { goto endgame; } else if (c == '.') { under = 0; |
︙ | ︙ | |||
1012 1013 1014 1015 1016 1017 1018 | } else { numSigDigs = 1; } numTrailZeros = 0; under = 0; state = FRACTION; break; | | | 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 | } else { numSigDigs = 1; } numTrailZeros = 0; under = 0; state = FRACTION; break; } else if (c == '_' && !(flags & TCL_PARSE_NO_UNDERSCORE)) { /* Ignore numeric "white space" */ under = 1; break; } goto endgame; case EXPONENT_START: |
︙ | ︙ | |||
1049 1050 1051 1052 1053 1054 1055 | */ if (isdigit(UCHAR(c))) { exponent = c - '0'; under = 0; state = EXPONENT; break; | | | 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 | */ if (isdigit(UCHAR(c))) { exponent = c - '0'; under = 0; state = EXPONENT; break; } else if (c == '_' && !(flags & TCL_PARSE_NO_UNDERSCORE)) { /* Ignore numeric "white space" */ under = 1; break; } goto endgame; case EXPONENT: |
︙ | ︙ | |||
1074 1075 1076 1077 1078 1079 1080 | exponent = 10 * exponent + (c - '0'); } else { exponent = LONG_MAX; } under = 0; state = EXPONENT; break; | | | 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 | exponent = 10 * exponent + (c - '0'); } else { exponent = LONG_MAX; } under = 0; state = EXPONENT; break; } else if (c == '_' && !(flags & TCL_PARSE_NO_UNDERSCORE)) { /* Ignore numeric "white space" */ under = 1; break; } goto endgame; /* |
︙ | ︙ |
︙ | ︙ | |||
106 107 108 109 110 111 112 | test get-3.4 {Tcl_GetDouble with iffy numbers} testdoubleobj { lmap x {0 0.0 " .0" ".0 " " 0e0 " "07" "- 0" "-0" "0o12" "0b10"} { catch {testdoubleobj set 1 $x} x set x } } {0.0 0.0 0.0 0.0 0.0 7.0 {expected floating-point number but got "- 0"} 0.0 10.0 2.0} test get-3.5 {tcl_GetInt with numeric whitespace (i.e. '_')} testgetint { | | | | 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 | test get-3.4 {Tcl_GetDouble with iffy numbers} testdoubleobj { lmap x {0 0.0 " .0" ".0 " " 0e0 " "07" "- 0" "-0" "0o12" "0b10"} { catch {testdoubleobj set 1 $x} x set x } } {0.0 0.0 0.0 0.0 0.0 7.0 {expected floating-point number but got "- 0"} 0.0 10.0 2.0} test get-3.5 {tcl_GetInt with numeric whitespace (i.e. '_')} testgetint { lmap x {0_0 " 1_0" "0_2 " " 3_3 " 14__23__32___4 " 0x_a " " 0_07 " " 0o_1_0 " " 0_b1_0 " _33 42_ 0_x15 0_o17 0_d19 } { catch {testgetint $x} x set x } } {0 10 2 33 1423324 10 7 8 {expected integer but got " 0_b1_0 "} {expected integer but got "_33"} {expected integer but got "42_"} {expected integer but got "0_x15"} {expected integer but got "0_o17"} {expected integer but got "0_d19"}} # cleanup ::tcltest::cleanupTests return # Local Variables: # mode: tcl |
︙ | ︙ |
︙ | ︙ | |||
551 552 553 554 555 556 557 558 559 560 561 562 563 564 | } -returnCodes 1 -result {unsigned bignum scans are invalid} test scan-5.19 {bigint scanning invalid} -setup { set a {}; } -body { list [scan "207698809136909011942886895" \ %llu a] $a } -result {1 207698809136909011942886895} test scan-6.1 {floating-point scanning} -setup { set a {}; set b {}; set c {}; set d {} } -body { list [scan "2.1 -3.0e8 .99962 a" "%f%g%e%f" a b c d] $a $b $c $d } -result {3 2.1 -300000000.0 0.99962 {}} test scan-6.2 {floating-point scanning} -setup { | > > > > > | 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 | } -returnCodes 1 -result {unsigned bignum scans are invalid} test scan-5.19 {bigint scanning invalid} -setup { set a {}; } -body { list [scan "207698809136909011942886895" \ %llu a] $a } -result {1 207698809136909011942886895} test scan-5.20 {ignore digit separators} -setup { set a {}; set b {}; set c {}; } -body { list [scan "10_23_45" %d_%d_%d a b c] $a $b $c } -result {3 10 23 45} test scan-6.1 {floating-point scanning} -setup { set a {}; set b {}; set c {}; set d {} } -body { list [scan "2.1 -3.0e8 .99962 a" "%f%g%e%f" a b c d] $a $b $c $d } -result {3 2.1 -300000000.0 0.99962 {}} test scan-6.2 {floating-point scanning} -setup { |
︙ | ︙ | |||
596 597 598 599 600 601 602 603 604 605 606 607 608 609 | list [scan "4.6abc" "%f %f %f %f" a b c d] $a $b $c $d } -result {1 4.6 {} {} {}} test scan-6.8 {floating-point scanning} -setup { set a {}; set b {}; set c {}; set d {} } -body { list [scan "4.6 5.2" "%f %f %f %f" a b c d] $a $b $c $d } -result {2 4.6 5.2 {} {}} test scan-7.1 {string and character scanning} -setup { set a {}; set b {}; set c {}; set d {} } -body { list [scan "abc defghijk dum " "%s %3s %20s %s" a b c d] $a $b $c $d } -result {4 abc def ghijk dum} test scan-7.2 {string and character scanning} -setup { | > > > > > | 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 | list [scan "4.6abc" "%f %f %f %f" a b c d] $a $b $c $d } -result {1 4.6 {} {} {}} test scan-6.8 {floating-point scanning} -setup { set a {}; set b {}; set c {}; set d {} } -body { list [scan "4.6 5.2" "%f %f %f %f" a b c d] $a $b $c $d } -result {2 4.6 5.2 {} {}} test scan-6.8 {disallow diget separator in floating-point} -setup { set a {}; set b {}; set c {}; } -body { list [scan "3.14_2.35_98.6" %f_%f_%f a b c ] $a $b $c } -result {3 3.14 2.35 98.6} test scan-7.1 {string and character scanning} -setup { set a {}; set b {}; set c {}; set d {} } -body { list [scan "abc defghijk dum " "%s %3s %20s %s" a b c d] $a $b $c $d } -result {4 abc def ghijk dum} test scan-7.2 {string and character scanning} -setup { |
︙ | ︙ |