Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
Comment: | array members only accessed using $, clean up lists, fix errors in ++ --, update manpage |
---|---|
Downloads: | Tarball | ZIP archive | SQL archive |
Timelines: | family | ancestors | trunk |
Files: | files | file ages | folders |
SHA3-256: |
994f7b267f3a82643c43e6a758a0e1a5 |
User & Date: | davebr 2024-05-09 16:19:52 |
Context
2024-05-09
| ||
16:19 | array members only accessed using $, clean up lists, fix errors in ++ --, update manpage Leaf check-in: 994f7b267f user: davebr tags: trunk | |
2024-04-06
| ||
13:42 | works,, list/vector/tcl array access will change check-in: f7694b2246 user: davebr tags: trunk | |
Changes
Changes to Makefile.
1 2 3 4 5 6 7 8 | # Make uexpr documentation, run tests all : test doc doc : uexpr.n uexpr.html test : uexpr.tcl uexpr.test | > > | 1 2 3 4 5 6 7 8 9 10 | # Make uexpr documentation, run tests # This works on my ststem. # The install directories need to be checked anywhere else. all : test doc doc : uexpr.n uexpr.html test : uexpr.tcl uexpr.test |
︙ | ︙ |
Changes to uexpr.dt.
1 2 | [manpage_begin uexpr n 0.1] [moddesc {Calculations with Units}] | | | 1 2 3 4 5 6 7 8 9 10 | [manpage_begin uexpr n 0.1] [moddesc {Calculations with Units}] [copyright {2021-2024 J.D Bruchie (BSD License)}] [titledesc {A Tcl package to evaluate and format expressions that may include values with units}] [require Tcl 8.6] [require uexpr [opt 0.1]] [require uexpr= [opt 0.1]] [description] |
︙ | ︙ | |||
32 33 34 35 36 37 38 | Operands can be numbers or numbers with units, sometimes saved in variables or a units dictionary. [package uexpr] does not operate on strings. Comparisons in [package uexpr] expressions return a numeric 1.0 for true, or 0.0 for false. [subsection Numbers] | | | > > > | | | 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 | Operands can be numbers or numbers with units, sometimes saved in variables or a units dictionary. [package uexpr] does not operate on strings. Comparisons in [package uexpr] expressions return a numeric 1.0 for true, or 0.0 for false. [subsection Numbers] Numbers start with a decimal point or digit, and may include only one decimal point. [example "1 1.23 .123"] They may include a 10's exponent after an e or E. The exponent may be signed. [example "1e-3 == 0.001"] Numbers can start after most operators or a space. [package uexpr] treats all numbers as double precision reals. When a string can be interpreted as a number it will be. [example "3e+5e == 300000*e"] The first "e" is part of a number. The second e is interpreted as a variable named e. A number followed by a variable with no operator in between implies a multiplication. [example "3e +5e == 3*e+5*e"] [example "3E+ 5e == 3*E+5*e"] Here the space breaks up the scientific number definition, so both "e" and "E" are interpreted as variables or units. Keep this in mind if scientific notation is used in an expression and units or variables named "e or "E" exist. [subsection "Unit Specifier"] |
︙ | ︙ | |||
80 81 82 83 84 85 86 | Values with units are simple expressions containing a pure number followed by (or multiplied by) a unit specifier. They are returned from [cmd uexpr], and can be saved in variables referenced by other [cmd uexpr] expressions. [example "100*BTU/hr ft^2"] | | | | | | | > > > > > > > > | > | < < < < < < < < < < < | > > > > > > > > > > > > > > > > | 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 | Values with units are simple expressions containing a pure number followed by (or multiplied by) a unit specifier. They are returned from [cmd uexpr], and can be saved in variables referenced by other [cmd uexpr] expressions. [example "100*BTU/hr ft^2"] defines a heat flux. [para] Internally the value with units is a list containing a magnitude and exponents on the basis units. For example using US Customary basis units: lbm, ft, sec, ... [example 3ft/sec] gives a list starting with 3 0 1 -1 ... [example 3m/sec] gives a list starting with 9.8425... 0 1 -1 ... Note both values are speeds, and both have the same exponents on their basis units. Compatible units have identical exponents on the basis units, and can be added, subtracted and compared. [subsection "Named Values"] Named values can be variables from the context of the call to uexpr, or units or constants from the units dictionary. Names must start with a "_" or alphabetic character, and can contain "_" "." and alphanumeric characters. If the variable is processed by [cmd uexpr], it will be treated as a subexpression, and its result inserted into the expression being evaluated. This allows variables to hold values with units, which are returned from [cmd uexpr] as simple expressions, not pure numbers. [subsection Variables] If a name follows a "$" it is a reference to a variable in the context of the uexpr call or a local variable in a function definition. If a name does not follow a "$" it may still be a variable (see [sectref "Name Resolution"]). If a variable name follows "$" the whole expression should be braced {}, to prevent Tcl from bypassing the variable handling by [cmd uexpr]. [para] Unlike the rest of Tcl, variable names referenced using "$" can also include "_" and "." characters. See [sectref "Name Resolution"]. [para] Tcl array members (such as a(b3) can only be read using "$". A reference without the "$" prefix will be interpreted as a function call, or an implied multiply of a variable by a subexpression. [subsection "Units and Constants"] Units and constants are defined in a units dictionary, common to all uexpr calls in an interpreter. If a name follows a single ":" it will only be looked up in the units dictionary. If a unit or constant name does not follow ":", it may be interpreted as a reference to a variable, see [sectref "Name Resolution"] below. [example ":ft"] look up the unit named ft in the units dictionary [subsection "Name Resolution"] If a name does not follow a "$" or ":" then the first value available from the following list is used: [list_begin itemized] [item] If the name is in an expression defining a function (see [cmd uexpr::func]) it is looked up in the function's parameter list [item] The name is looked up as a variable in the context of the uexpr call. [item] The name is looked up in the units dictionary. [item] An error occurs if no value was found. [list_end] If the name follows a "$" it is not looked up in the units dictionary. A name following a "$" can be a Tcl array member reference. [example {uexpr {3*$a(b)+1}}] will multiply the value of the Tcl array "a" member "b" by 3 then add 1. Very similar to what Tcl [cmd expr] would do. Without the "$" uexpr will interpret (b) as a subexpression or possibly part of a function call. [example {uexpr {3*a(b)+1}}] where a is a variable (and not a function name) multiplies 3, a and b, then adds 1 [subsection "Named Values and Implied Multiply"] A number immediately followed by a named value will be interpreted as if there was an implied multiply between them. [example 3ft+1inch] |
︙ | ︙ | |||
187 188 189 190 191 192 193 | [package uexpr] does not deal with integers and bitwise operators (& | ~ << >>), or boolean operators (! && ||) or strings (eq ne lt le ge gt), or list searches (in ni). Operators roughly in descending order of precedence: [list_begin itemized] | | | > > > | > > | > | > > > > | | | | | > | > > | > > > > | > > > > > > > | > | | 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 | [package uexpr] does not deal with integers and bitwise operators (& | ~ << >>), or boolean operators (! && ||) or strings (eq ne lt le ge gt), or list searches (in ni). Operators roughly in descending order of precedence: [list_begin itemized] [item] .. index into a list or nested lists in a variable. [example "set v {1 2 3 4}"] saves a list in the variable v [example "= v..2"] returns the 3rd list entry (3 in this case) note the list could have been generated using the [cmd uexpr] "," operator too: [example "set v [lb]= 1,2,3,4[rb]"] To index into nested lists use a list of index values: [example "set m [lb]= (1,2,3),(11,22,33),(111,222,333)[rb]"] saves a 3x3 nested list in m [example "=m..(2,0)"] returns a value of 111. [para] This is rather fragile. If the indexes do not match the actual nested list structure, there may not be any error, and odd results will be returned. [item] ** ^ exponentiation. The right argument (exponent) must be unitless. The left argument can have units. However if the exponent is not an integer, and the left operand has units, the result basis unit exponents may not be integers. In some cases this can cause problems. see [sectref "Fractional or Non-Integer Exponents"]. Note exponentiations group to the right so: [example "2^3^2 == 2^(3^2) == 512"] [item] "implied multiply" multiplication is implied any time two values are not separated by an operator. A space between values is common, however a number followed by a named value also implies multiplication. Implied multiply has a higher precedence than multiply or divide. Useful in unit specifiers. [item] / divide, has same precedence as multiply. [item] * multiply, has same precedence as divide. [item] Unary + - operators to the left of their operand have a minimum precedence between addition or subtraction and multiplication. However their effective precedence is the maximum of their precedence and the precedence of the operator to their left (if any). This means: [example "-2^2 == 0-2^2 == -4"] This is not true for Tcl [cmd expr], however it appears to match the conventions in "Handbook of Mathematical Functions" by Abramowitz and Stegun. See the definition of the error function for an example. [item] + - ++ -- addition and subtraction, left and right arguments must have compatible units. ++ and -- will also insert a line break in LaTeX formatted output. [item] < <= =< == != <> >= => > comparisons, return 1.0 for true, 0.0 for false. note ==, != and <> are not that useful with real numbers. Left and right arguments must have compatible units. [para] Comparisons can be chained. The result is true if all comparisons are true. [example 7<a<13] will return true (1.0) if the value of a is between 7 and 13 [item] "," separate items in a list. Used to build lists and nested lists, such as lists of function arguments: [example hypot(a,b)] Also can be used to generate lists to use with the .. operator. For example coefficients for a polynomial using the sum function: [example {set C [uexpr 1,2,3,4]}] [example "set x 0.1"] [example "uexpr sum(i,0,3,C..i*x^i)"] will return 1.234 [item] "=" marks a result unit specifier for an expression. For example: [example {uexpr 15 lbf/3 in^2=psi}] would return "5.0 psi" Multiple result specifiers can be given: [example {uexpr::ltxExpr {15 N / 5 cm^2 = bar = Pa}}] would return results in bar and pascals. [list_end] "( )" group subexpressions (as for [cmd expr]). [para] "%" is a constant defined as 0.01 in the units dict. [example "45%*a = 0.45*a"] [section "Math Functions"] Most math functions that work on real numbers in [cmd expr] are available in [package uexpr]. However they are now unit aware. [list_begin itemized] |
︙ | ︙ | |||
318 319 320 321 322 323 324 | will be left with its last value [example "sum(i,0,4,2^i)"] will return 31.0 [para] | | | | | | 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 | will be left with its last value [example "sum(i,0,4,2^i)"] will return 31.0 [para] A polynomial with coefficients in c [example {set c [= 1,2,3,4,5]}] [example {set x 10}] [example "= sum(i,0,4,c..i*x^i)"] will return 54321.0 [item] [fun hasUnits(expression)] returns 0 if the value is a pure number. [example hasUnits(3)] returns 0 |
︙ | ︙ | |||
369 370 371 372 373 374 375 | If the expression ends with "=" followed by a unit specifier, the result is formatted to use those units. If the units specified do not match the actual result units, they will be multiplied or divided by enough base units to make them match. If multiple result unit specifiers are given only the last is used. If no result units are specified, the resulting unit expression will contain only base units. In any | | | 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 | If the expression ends with "=" followed by a unit specifier, the result is formatted to use those units. If the units specified do not match the actual result units, they will be multiplied or divided by enough base units to make them match. If multiple result unit specifiers are given only the last is used. If no result units are specified, the resulting unit expression will contain only base units. In any case the result can be used in an expression evaluated by uexpr. [para] [para] [cmd [lb]uexpr[rb]] functions similarly to [cmd expr] with several significant differences. It is not a replacement for [cmd expr]. For example: |
︙ | ︙ | |||
427 428 429 430 431 432 433 434 435 436 | return a list of all units matching the glob pattern. Return all unit names if no pattern is given. [call [cmd uexpr::unitsLike] [opt [arg expression]]] Returns a sorted list of all units and constants with the same dimensions (compatible units) as the result of the given expression. If no expression is given, all unitless constants are returned. [call [cmd uexpr::newUnit] [arg name] [arg expression]] | > > > > > > > > > > | | | | | 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 | return a list of all units matching the glob pattern. Return all unit names if no pattern is given. [call [cmd uexpr::unitsLike] [opt [arg expression]]] Returns a sorted list of all units and constants with the same dimensions (compatible units) as the result of the given expression. If no expression is given, all unitless constants are returned. [para] For example if no new units are defined: [example {uexpr::unitsLike psi}] returns: atm bar Pa pascal psi Torr [para] [example {uexpr::unitsLike BTU/hr}] returns: hp watt [call [cmd uexpr::newUnit] [arg name] [arg expression]] [cmd uexpr::newUnit] adds another unit to the unit dictionary. The expression defines the new unit in terms of any existing base or defined unit. The unit dictionary is common to all contexts within a Tcl interpreter. These changes are global. For example a mile is defined as: [example "uexpr::newUnit mile 5280*ft"] Note, units are very similar to variables, except: variables are looked up in the calling context of the expression evaluator. Unit definitions are global, available to any instance of uexpr in the same Tcl interpreter. [call [cmd uexpr::func] [arg name] [arg "parameter_names"] [arg expression]] [cmd uexpr::func] adds a new function, usable in any uexpr call in the same interpreter. Variables in the expression that are not in the parameter list, have the value of the variable in the calling context at the time the function is defined. Variables in the parameter list have values set at the time the function is used. If not all the parameters are supplied in the function call, the value of a variable |
︙ | ︙ | |||
492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 | [example_begin] set c 33 uexpr ss(4,5) [example_end] returns 12 (4+5+3), the change in c is ignored as it was not a function parameter [list_end] [section "Differences between [cmd uexpr] and [cmd expr]"] [cmd uexpr] is not a replacement for [cmd expr]. [list_begin itemized] [item] numbers are all double precision. Mostly because many unit conversion factors are not integers. | > > > > > > > | | | | | 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 | [example_begin] set c 33 uexpr ss(4,5) [example_end] returns 12 (4+5+3), the change in c is ignored as it was not a function parameter [call [cmd uexpr::uset] [arg name] [arg "expression"]] [cmd uexpr::uset] writes the result of evaluating an expression to a variable, tcl array member or into a list or nested list saved in a variable. [list_end] [section "Differences between [cmd uexpr] and [cmd expr]"] [cmd uexpr] is not a replacement for [cmd expr]. [list_begin itemized] [item] numbers are all double precision. Mostly because many unit conversion factors are not integers. [item] Variable names without "$" or ":" prefixes will be looked up in the calling context or the units dictionary. [item] variables used in expressions can contain complete expressions, not just numbers. The variable contents will be evaluated and its value used in the expression. This will likely not work if Tcl gets to the variable first. Any expression that refers to variables with the "$" prefix should be within braces. [item] a variable can contain a list or nested lists of unit expressions. To get a value from a list, or nested list use the ".." operator. [item] There are no bit or logical operations (<< >> & | && ||). |
︙ | ︙ | |||
535 536 537 538 539 540 541 | Or using ltxExpr, to get the results in cm and feet: [example "[cmd uexpr::ltxExpr] 4*in+3*in=cm=ft"] [item] There are no type conversion functions (double entier, wide). int() exists, and returns a double with an integer value. | | | | 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 | Or using ltxExpr, to get the results in cm and feet: [example "[cmd uexpr::ltxExpr] 4*in+3*in=cm=ft"] [item] There are no type conversion functions (double entier, wide). int() exists, and returns a double with an integer value. [item] all functions available to uexpr and friends are in the ::uexpr::ufunc namespace. [item] When two values are not separated by an operator there is an implied multiply. Since variable names cannot start with a number or decimal point, "3a" will be interpreted as "3 a" or "5ft" as "5 ft". Implied multiplies have higher precedence than normal multiply (*) or divide (/). |
︙ | ︙ | |||
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 | [example "[cmd uexpr] 20*degC+zdc-zdf=degF"] Similar logic holds for gauge, absolute and differential pressures. The offset from absolute to gauge pressure is atm (one atmosphere). [subsection "Customary and Self Consistent Units"] An expression and its result have self consistent units if the result magnitude does not change when units are removed from all values in the expression (as would happen if evaluated with [cmd expr]). For example the volume of a rectangular solid: [example v=h*w*l] if h, w, l are given in inches, and the volume is given in cubic inches (in^3) the units are self consistent. Given this self consistent formula, the [package uexpr] package can calculate the volume in any desired units as long as h, w and l are all given in length units. [para] Many common formulas use customary units. To use them with this package they need to be written in self consistent form. One simple way to do this is divide all variables in the expression by the units | > > | | 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 | [example "[cmd uexpr] 20*degC+zdc-zdf=degF"] Similar logic holds for gauge, absolute and differential pressures. The offset from absolute to gauge pressure is atm (one atmosphere). [subsection "Customary and Self Consistent Units"] An expression and its result have self consistent units if the result magnitude does not change when units are removed from all values in the expression (as would happen if evaluated with [cmd expr]). For example the volume of a rectangular solid: [example v=h*w*l] if h, w, l are given in inches, and the volume is given in cubic inches (in^3) the units are self consistent. Given this self consistent formula, the [package uexpr] package can calculate the volume in any desired units as long as h, w and l are all given in length units. [para] Many common formulas use customary units. To use them with this package they need to be written in self consistent form. One simple way to do this is divide all variables in the expression by the units specified, then multiply the expression by the specified result units. For example a formula to calculate liquid flow through a valve for simple cases (no cavitation or flashing, low viscosity) is: [example "Q = Cv*sqrt(DP/Sg)"] where |
︙ | ︙ | |||
684 685 686 687 688 689 690 | compressibility of gases, use 1.0 for liquids or gases when the operating pressure is much higher that the pressure drop. [example "w = 0.09970190*C.d*Y*d^2*sqrt((DP*den)/(1-(d/D)^4))"] The self consistent version of this formula is: | | | | 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 | compressibility of gases, use 1.0 for liquids or gases when the operating pressure is much higher that the pressure drop. [example "w = 0.09970190*C.d*Y*d^2*sqrt((DP*den)/(1-(d/D)^4))"] The self consistent version of this formula is: [example "w = sqrt(2)*C.d*Y*pi/4*d^2*sqrt((DP*den)/(1-(d/D)^4))"] the only vaguely mysterious number in the self consistent formula is sqrt(2). It will work with inputs and results in any compatible units. Meter inlet and throat size in any combination of ft, in, cm... DP could be given as psi, Pascal, bar, mm mercury ... Density (den) could be given as lbm/ft^3, gram/cm^3 ... The resulting mass flow rate can be lbm/hr gram/sec ... [para] Often a formula for SI units will be self consistent, mass Kg, lengths meter, time sec, force newton (N), pressure Pascal (Pa) ... But it needs to be checked. [subsection "Fractional or Non-Integer Exponents"] In practical formulas, fractional exponents on values with units rarely occur. Although mathematicians and physicists keep trying. |
︙ | ︙ |
Changes to uexpr.html.
︙ | ︙ | |||
89 90 91 92 93 94 95 | margin-bottom: 1em; border-bottom: 1px solid black; } --></style> </head> <!-- Generated from file 'uexpr.dt' by tcllib/doctools with format 'html' --> | | | 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 | margin-bottom: 1em; border-bottom: 1px solid black; } --></style> </head> <!-- Generated from file 'uexpr.dt' by tcllib/doctools with format 'html' --> <!-- Copyright &copy; 2021-2024 J.D Bruchie (BSD License) --> <!-- uexpr.n --> <body><div class="doctools"> <h1 class="doctools_title">uexpr(n) 0.1 uexpr "Calculations with Units"</h1> <div id="name" class="doctools_section"><h2><a name="name">Name</a></h2> <p>uexpr - A Tcl package to evaluate and format expressions that may include values with units</p> |
︙ | ︙ | |||
144 145 146 147 148 149 150 | </ul> <ul class="doctools_syntax"> <li><a href="#1"><b class="cmd">uexpr</b> <i class="arg">expression</i> <span class="opt">?<i class="arg">expressions</i>?</span> ...</a></li> <li><a href="#2"><b class="cmd">::uexpr::ltxExpr</b> <i class="arg">expression</i> <span class="opt">?<i class="arg">expression</i>?</span> ...</a></li> <li><a href="#3"><b class="cmd">uexpr::unitNames</b> <span class="opt">?<i class="arg">glob pattern</i>?</span></a></li> <li><a href="#4"><b class="cmd">uexpr::unitsLike</b> <span class="opt">?<i class="arg">expression</i>?</span></a></li> <li><a href="#5"><b class="cmd">uexpr::newUnit</b> <i class="arg">name</i> <i class="arg">expression</i></a></li> | | > | 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 | </ul> <ul class="doctools_syntax"> <li><a href="#1"><b class="cmd">uexpr</b> <i class="arg">expression</i> <span class="opt">?<i class="arg">expressions</i>?</span> ...</a></li> <li><a href="#2"><b class="cmd">::uexpr::ltxExpr</b> <i class="arg">expression</i> <span class="opt">?<i class="arg">expression</i>?</span> ...</a></li> <li><a href="#3"><b class="cmd">uexpr::unitNames</b> <span class="opt">?<i class="arg">glob pattern</i>?</span></a></li> <li><a href="#4"><b class="cmd">uexpr::unitsLike</b> <span class="opt">?<i class="arg">expression</i>?</span></a></li> <li><a href="#5"><b class="cmd">uexpr::newUnit</b> <i class="arg">name</i> <i class="arg">expression</i></a></li> <li><a href="#6"><b class="cmd">uexpr::func</b> <i class="arg">name</i> <i class="arg">parameter_names</i> <i class="arg">expression</i></a></li> <li><a href="#7"><b class="cmd">uexpr::uset</b> <i class="arg">name</i> <i class="arg">expression</i></a></li> </ul> </div> </div> <div id="section1" class="doctools_section"><h2><a name="section1">Description</a></h2> <p>Package <b class="package">uexpr</b> provides commands to evaluate and format expressions that may include values with units. Its intended to be used as part of a program to document engineering, physical and |
︙ | ︙ | |||
171 172 173 174 175 176 177 | </div> <div id="section2" class="doctools_section"><h2><a name="section2">Operands</a></h2> <p>Operands can be numbers or numbers with units, sometimes saved in variables or a units dictionary. <b class="package">uexpr</b> does not operate on strings. Comparisons in <b class="package">uexpr</b> expressions return a numeric 1.0 for true, or 0.0 for false.</p> <div id="subsection1" class="doctools_subsection"><h3><a name="subsection1">Numbers</a></h3> | | < | | | 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 | </div> <div id="section2" class="doctools_section"><h2><a name="section2">Operands</a></h2> <p>Operands can be numbers or numbers with units, sometimes saved in variables or a units dictionary. <b class="package">uexpr</b> does not operate on strings. Comparisons in <b class="package">uexpr</b> expressions return a numeric 1.0 for true, or 0.0 for false.</p> <div id="subsection1" class="doctools_subsection"><h3><a name="subsection1">Numbers</a></h3> <p>Numbers start with a decimal point or digit, and may include only one decimal point.</p> <pre class="doctools_example">1 1.23 .123</pre> <p>They may include a 10's exponent after an e or E. The exponent may be signed.</p> <pre class="doctools_example">1e-3 == 0.001</pre> <p>Numbers can start after most operators or a space. <b class="package">uexpr</b> treats all numbers as double precision reals. When a string can be interpreted as a number it will be.</p> <pre class="doctools_example">3e+5e == 300000*e</pre> <p>The first "e" is part of a number. The second e is interpreted as a variable named e. A number followed by a variable with no operator in between implies a multiplication.</p> <pre class="doctools_example">3e +5e == 3*e+5*e</pre> <pre class="doctools_example">3E+ 5e == 3*E+5*e</pre> <p>Here the space breaks up the scientific number definition, so both "e" and "E" are interpreted as variables or units. Keep this in mind if scientific notation is used in an expression and units or variables named "e or "E" exist.</p> </div> <div id="subsection2" class="doctools_subsection"><h3><a name="subsection2">Unit Specifier</a></h3> <p>A unit specifier is a simple expression containing only multiply (including implied multiply) and divide operators, numbers, unit names and variable names.</p> |
︙ | ︙ | |||
207 208 209 210 211 212 213 | </div> <div id="subsection3" class="doctools_subsection"><h3><a name="subsection3">Values with Units</a></h3> <p>Values with units are simple expressions containing a pure number followed by (or multiplied by) a unit specifier. They are returned from <b class="cmd">uexpr</b>, and can be saved in variables referenced by other <b class="cmd">uexpr</b> expressions.</p> <pre class="doctools_example">100*BTU/hr ft^2</pre> | | | | | | | > > > > > | > | < < < < < < < < < | > > > > > > > > | | | > > > | > | > | > > > > | | | | | > > > > > > > | > | > > | | | | | 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 | </div> <div id="subsection3" class="doctools_subsection"><h3><a name="subsection3">Values with Units</a></h3> <p>Values with units are simple expressions containing a pure number followed by (or multiplied by) a unit specifier. They are returned from <b class="cmd">uexpr</b>, and can be saved in variables referenced by other <b class="cmd">uexpr</b> expressions.</p> <pre class="doctools_example">100*BTU/hr ft^2</pre> <p>defines a heat flux.</p> <p>Internally the value with units is a list containing a magnitude and exponents on the basis units. For example using US Customary basis units: lbm, ft, sec, ...</p> <pre class="doctools_example">3ft/sec</pre> <p>gives a list starting with 3 0 1 -1 ...</p> <pre class="doctools_example">3m/sec</pre> <p>gives a list starting with 9.8425... 0 1 -1 ... Note both values are speeds, and both have the same exponents on their basis units. Compatible units have identical exponents on the basis units, and can be added, subtracted and compared.</p> </div> <div id="subsection4" class="doctools_subsection"><h3><a name="subsection4">Named Values</a></h3> <p>Named values can be variables from the context of the call to uexpr, or units or constants from the units dictionary. Names must start with a "_" or alphabetic character, and can contain "_" "." and alphanumeric characters. If the variable is processed by <b class="cmd">uexpr</b>, it will be treated as a subexpression, and its result inserted into the expression being evaluated. This allows variables to hold values with units, which are returned from <b class="cmd">uexpr</b> as simple expressions, not pure numbers.</p> </div> <div id="subsection5" class="doctools_subsection"><h3><a name="subsection5">Variables</a></h3> <p>If a name follows a "$" it is a reference to a variable in the context of the uexpr call or a local variable in a function definition. If a name does not follow a "$" it may still be a variable (see <span class="sectref"><a href="#subsection7">Name Resolution</a></span>). If a variable name follows "$" the whole expression should be braced {}, to prevent Tcl from bypassing the variable handling by <b class="cmd">uexpr</b>.</p> <p>Unlike the rest of Tcl, variable names referenced using "$" can also include "_" and "." characters. See <span class="sectref"><a href="#subsection7">Name Resolution</a></span>.</p> <p>Tcl array members (such as a(b3) can only be read using "$". A reference without the "$" prefix will be interpreted as a function call, or an implied multiply of a variable by a subexpression.</p> </div> <div id="subsection6" class="doctools_subsection"><h3><a name="subsection6">Units and Constants</a></h3> <p>Units and constants are defined in a units dictionary, common to all uexpr calls in an interpreter. If a name follows a single ":" it will only be looked up in the units dictionary. If a unit or constant name does not follow ":", it may be interpreted as a reference to a variable, see <span class="sectref"><a href="#subsection7">Name Resolution</a></span> below.</p> <pre class="doctools_example">:ft</pre> <p>look up the unit named ft in the units dictionary</p> </div> <div id="subsection7" class="doctools_subsection"><h3><a name="subsection7">Name Resolution</a></h3> <p>If a name does not follow a "$" or ":" then the first value available from the following list is used:</p> <ul class="doctools_itemized"> <li><p>If the name is in an expression defining a function (see <b class="cmd">uexpr::func</b>) it is looked up in the function's parameter list</p></li> <li><p>The name is looked up as a variable in the context of the uexpr call.</p></li> <li><p>The name is looked up in the units dictionary.</p></li> <li><p>An error occurs if no value was found.</p></li> </ul> <p>If the name follows a "$" it is not looked up in the units dictionary. A name following a "$" can be a Tcl array member reference.</p> <pre class="doctools_example">uexpr {3*$a(b)+1}</pre> <p>will multiply the value of the Tcl array "a" member "b" by 3 then add 1. Very similar to what Tcl <b class="cmd">expr</b> would do. Without the "$" uexpr will interpret (b) as a subexpression or possibly part of a function call.</p> <pre class="doctools_example">uexpr {3*a(b)+1}</pre> <p>where a is a variable (and not a function name) multiplies 3, a and b, then adds 1</p> </div> <div id="subsection8" class="doctools_subsection"><h3><a name="subsection8">Named Values and Implied Multiply</a></h3> <p>A number immediately followed by a named value will be interpreted as if there was an implied multiply between them.</p> <pre class="doctools_example">3ft+1inch</pre> <p>will be interpreted as 3*ft+1*inch. Note implied multiply has a higher precedence than divide so:</p> <pre class="doctools_example">diam/3ft</pre> <p>or</p> <pre class="doctools_example">diam/3 ft</pre> <p>will be interpreted as diam/(3*ft).</p> </div> </div> <div id="section3" class="doctools_section"><h2><a name="section3">Operators</a></h2> <p><b class="package">uexpr</b> does not deal with integers and bitwise operators (& | ~ << >>), or boolean operators (! && ||) or strings (eq ne lt le ge gt), or list searches (in ni). Operators roughly in descending order of precedence:</p> <ul class="doctools_itemized"> <li><p>.. index into a list or nested lists in a variable.</p> <pre class="doctools_example">set v {1 2 3 4}</pre> <p>saves a list in the variable v</p> <pre class="doctools_example">= v..2</pre> <p>returns the 3rd list entry (3 in this case) note the list could have been generated using the <b class="cmd">uexpr</b> "," operator too:</p> <pre class="doctools_example">set v [= 1,2,3,4]</pre> <p>To index into nested lists use a list of index values:</p> <pre class="doctools_example">set m [= (1,2,3),(11,22,33),(111,222,333)]</pre> <p>saves a 3x3 nested list in m</p> <pre class="doctools_example">=m..(2,0)</pre> <p>returns a value of 111.</p> <p>This is rather fragile. If the indexes do not match the actual nested list structure, there may not be any error, and odd results will be returned.</p></li> <li><p>** ^ exponentiation. The right argument (exponent) must be unitless. The left argument can have units. However if the exponent is not an integer, and the left operand has units, the result basis unit exponents may not be integers. In some cases this can cause problems. see <span class="sectref"><a href="#subsection12">Fractional or Non-Integer Exponents</a></span>. Note exponentiations group to the right so:</p> <pre class="doctools_example">2^3^2 == 2^(3^2) == 512</pre> </li> <li><p>"implied multiply" multiplication is implied any time two values are not separated by an operator. A space between values is common, however a number followed by a named value also implies multiplication. Implied multiply has a higher precedence than multiply or divide. Useful in unit specifiers.</p></li> <li><p>/ divide, has same precedence as multiply.</p></li> <li><p>* multiply, has same precedence as divide.</p></li> <li><p>Unary + - operators to the left of their operand have a minimum precedence between addition or subtraction and multiplication. However their effective precedence is the maximum of their precedence and the precedence of the operator to their left (if any). This means:</p> <pre class="doctools_example">-2^2 == 0-2^2 == -4</pre> <p>This is not true for Tcl <b class="cmd">expr</b>, however it appears to match the conventions in "Handbook of Mathematical Functions" by Abramowitz and Stegun. See the definition of the error function for an example.</p></li> <li><p>+ - ++ -- addition and subtraction, left and right arguments must have compatible units. ++ and -- will also insert a line break in LaTeX formatted output.</p></li> <li><p>< <= =< == != <> >= => > comparisons, return 1.0 for true, 0.0 for false. note ==, != and <> are not that useful with real numbers. Left and right arguments must have compatible units.</p> <p>Comparisons can be chained. The result is true if all comparisons are true.</p> <pre class="doctools_example">7<a<13</pre> <p>will return true (1.0) if the value of a is between 7 and 13</p></li> <li><p>"," separate items in a list. Used to build lists and nested lists, such as lists of function arguments:</p> <pre class="doctools_example">hypot(a,b)</pre> <p>Also can be used to generate lists to use with the .. operator. For example coefficients for a polynomial using the sum function:</p> <pre class="doctools_example">set C [uexpr 1,2,3,4]</pre> <pre class="doctools_example">set x 0.1</pre> <pre class="doctools_example">uexpr sum(i,0,3,C..i*x^i)</pre> <p>will return 1.234</p></li> <li><p>"=" marks a result unit specifier for an expression. For example:</p> <pre class="doctools_example">uexpr 15 lbf/3 in^2=psi</pre> <p>would return "5.0 psi" Multiple result specifiers can be given:</p> <pre class="doctools_example">uexpr::ltxExpr {15 N / 5 cm^2 = bar = Pa}</pre> <p>would return results in bar and pascals.</p></li> </ul> <p>"( )" group subexpressions (as for <b class="cmd">expr</b>).</p> <p>"%" is a constant defined as 0.01 in the units dict.</p> <pre class="doctools_example">45%*a = 0.45*a</pre> </div> <div id="section4" class="doctools_section"><h2><a name="section4">Math Functions</a></h2> <p>Most math functions that work on real numbers in <b class="cmd">expr</b> are available in <b class="package">uexpr</b>. However they are now unit aware.</p> <ul class="doctools_itemized"> <li><p>Trigonometric Functions (sin cos tan) expect a single angle argument (deg rad ...).and return a pure number.</p></li> <li><p>Inverse Trigonometric Functions (asin acos atan) expect a pure |
︙ | ︙ | |||
366 367 368 369 370 371 372 | <li><p><b class="function">sum(var,first_index,last_index,expression)</b> sums an expression for a range of index variable values. The first and last index values must be unitless. they will be rounded to the nearest integer. The index variable will be created if it does not exist, and will be left with its last value</p> <pre class="doctools_example">sum(i,0,4,2^i)</pre> <p>will return 31.0</p> | | | | | | 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 | <li><p><b class="function">sum(var,first_index,last_index,expression)</b> sums an expression for a range of index variable values. The first and last index values must be unitless. they will be rounded to the nearest integer. The index variable will be created if it does not exist, and will be left with its last value</p> <pre class="doctools_example">sum(i,0,4,2^i)</pre> <p>will return 31.0</p> <p>A polynomial with coefficients in c</p> <pre class="doctools_example">set c [= 1,2,3,4,5]</pre> <pre class="doctools_example">set x 10</pre> <pre class="doctools_example">= sum(i,0,4,c..i*x^i)</pre> <p>will return 54321.0</p></li> <li><p><b class="function">hasUnits(expression)</b> returns 0 if the value is a pure number.</p> <pre class="doctools_example">hasUnits(3)</pre> <p>returns 0</p></li> <li><p><b class="function">units(expression)</b> returns the basis units of the result of the expression, with a magnitude of 1. For example any value with units of a pressure (psi, bar, Pascal ...) will return the same unit expression:</p> |
︙ | ︙ | |||
401 402 403 404 405 406 407 | then evaluates the expression and returns one result. If the expression ends with "=" followed by a unit specifier, the result is formatted to use those units. If the units specified do not match the actual result units, they will be multiplied or divided by enough base units to make them match. If multiple result unit specifiers are given only the last is used. If no result units are specified, the resulting unit expression will contain only base units. In any | | | 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 | then evaluates the expression and returns one result. If the expression ends with "=" followed by a unit specifier, the result is formatted to use those units. If the units specified do not match the actual result units, they will be multiplied or divided by enough base units to make them match. If multiple result unit specifiers are given only the last is used. If no result units are specified, the resulting unit expression will contain only base units. In any case the result can be used in an expression evaluated by uexpr.</p> <p><b class="cmd">[uexpr]</b> functions similarly to <b class="cmd">expr</b> with several significant differences. It is not a replacement for <b class="cmd">expr</b>. For example:</p> <pre class="doctools_example">uexpr 4*in+5*cm=mm</pre> <p>returns something close to "151.6000...002 mm"</p> <pre class="doctools_example">uexpr 15 psi = in wc = bar</pre> <p>returns something like "1.0342...41 bar", note only the last result unit specifier was used</p> |
︙ | ︙ | |||
440 441 442 443 444 445 446 | <dt><a name="3"><b class="cmd">uexpr::unitNames</b> <span class="opt">?<i class="arg">glob pattern</i>?</span></a></dt> <dd><p>return a list of all units matching the glob pattern. Return all unit names if no pattern is given.</p></dd> <dt><a name="4"><b class="cmd">uexpr::unitsLike</b> <span class="opt">?<i class="arg">expression</i>?</span></a></dt> <dd><p>Returns a sorted list of all units and constants with the same dimensions (compatible units) as the result of the given expression. If no expression is | | > > > > > | | | | | 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 | <dt><a name="3"><b class="cmd">uexpr::unitNames</b> <span class="opt">?<i class="arg">glob pattern</i>?</span></a></dt> <dd><p>return a list of all units matching the glob pattern. Return all unit names if no pattern is given.</p></dd> <dt><a name="4"><b class="cmd">uexpr::unitsLike</b> <span class="opt">?<i class="arg">expression</i>?</span></a></dt> <dd><p>Returns a sorted list of all units and constants with the same dimensions (compatible units) as the result of the given expression. If no expression is given, all unitless constants are returned.</p> <p>For example if no new units are defined:</p> <pre class="doctools_example">uexpr::unitsLike psi</pre> <p>returns: atm bar Pa pascal psi Torr</p> <pre class="doctools_example">uexpr::unitsLike BTU/hr</pre> <p>returns: hp watt</p></dd> <dt><a name="5"><b class="cmd">uexpr::newUnit</b> <i class="arg">name</i> <i class="arg">expression</i></a></dt> <dd><p><b class="cmd">uexpr::newUnit</b> adds another unit to the unit dictionary. The expression defines the new unit in terms of any existing base or defined unit. The unit dictionary is common to all contexts within a Tcl interpreter. These changes are global. For example a mile is defined as:</p> <pre class="doctools_example">uexpr::newUnit mile 5280*ft</pre> <p>Note, units are very similar to variables, except: variables are looked up in the calling context of the expression evaluator. Unit definitions are global, available to any instance of uexpr in the same Tcl interpreter.</p></dd> <dt><a name="6"><b class="cmd">uexpr::func</b> <i class="arg">name</i> <i class="arg">parameter_names</i> <i class="arg">expression</i></a></dt> <dd><p><b class="cmd">uexpr::func</b> adds a new function, usable in any uexpr call in the same interpreter. Variables in the expression that are not in the parameter list, have the value of the variable in the calling context at the time the function is defined. Variables in the parameter list have values set at the time the function is used. If not all the parameters are supplied in the function call, the value of a variable with the same name (in the calling context) is used (it came from |
︙ | ︙ | |||
490 491 492 493 494 495 496 497 498 499 500 501 502 503 | </pre> <p>returns 29 (4+22+3), b is not given, so the current value is used</p> <pre class="doctools_example"> set c 33 uexpr ss(4,5) </pre> <p>returns 12 (4+5+3), the change in c is ignored as it was not a function parameter</p></dd> </dl> </div> <div id="section6" class="doctools_section"><h2><a name="section6">Differences between <b class="cmd">uexpr</b> and <b class="cmd">expr</b></a></h2> <p><b class="cmd">uexpr</b> is not a replacement for <b class="cmd">expr</b>.</p> <ul class="doctools_itemized"> <li><p>numbers are all double precision. Mostly because many unit conversion factors are not integers.</p></li> | > > > > | | | | | | | 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 | </pre> <p>returns 29 (4+22+3), b is not given, so the current value is used</p> <pre class="doctools_example"> set c 33 uexpr ss(4,5) </pre> <p>returns 12 (4+5+3), the change in c is ignored as it was not a function parameter</p></dd> <dt><a name="7"><b class="cmd">uexpr::uset</b> <i class="arg">name</i> <i class="arg">expression</i></a></dt> <dd><p><b class="cmd">uexpr::uset</b> writes the result of evaluating an expression to a variable, tcl array member or into a list or nested list saved in a variable.</p></dd> </dl> </div> <div id="section6" class="doctools_section"><h2><a name="section6">Differences between <b class="cmd">uexpr</b> and <b class="cmd">expr</b></a></h2> <p><b class="cmd">uexpr</b> is not a replacement for <b class="cmd">expr</b>.</p> <ul class="doctools_itemized"> <li><p>numbers are all double precision. Mostly because many unit conversion factors are not integers.</p></li> <li><p>Variable names without "$" or ":" prefixes will be looked up in the calling context or the units dictionary.</p></li> <li><p>variables used in expressions can contain complete expressions, not just numbers. The variable contents will be evaluated and its value used in the expression. This will likely not work if Tcl gets to the variable first. Any expression that refers to variables with the "$" prefix should be within braces.</p></li> <li><p>a variable can contain a list or nested lists of unit expressions. To get a value from a list, or nested list use the ".." operator.</p></li> <li><p>There are no bit or logical operations (<< >> & | && ||).</p></li> <li><p>a new operator "=" indicates a result unit specifier follows. For example to add two lengths in inches, and return the result in centimeters:</p> <pre class="doctools_example"><b class="cmd">uexpr</b> 4*in+5*in=cm</pre> <p>Or using ltxExpr, to get the results in cm and feet:</p> <pre class="doctools_example"><b class="cmd">uexpr::ltxExpr</b> 4*in+3*in=cm=ft</pre> </li> <li><p>There are no type conversion functions (double entier, wide). int() exists, and returns a double with an integer value.</p></li> <li><p>all functions available to uexpr and friends are in the ::uexpr::ufunc namespace.</p></li> <li><p>When two values are not separated by an operator there is an implied multiply. Since variable names cannot start with a number or decimal point, "3a" will be interpreted as "3 a" or "5ft" as "5 ft". Implied multiplies have higher precedence than normal multiply (*) or divide (/).</p> <pre class="doctools_example">3 ft/3 in == (3*ft)/(3*in) == 12.0</pre> <p>However</p> |
︙ | ︙ | |||
591 592 593 594 595 596 597 | inches (in^3) the units are self consistent. Given this self consistent formula, the <b class="package">uexpr</b> package can calculate the volume in any desired units as long as h, w and l are all given in length units.</p> <p>Many common formulas use customary units. To use them with this package they need to be written in self consistent form. One simple way to do this is divide all variables in the expression by the units | | | 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 | inches (in^3) the units are self consistent. Given this self consistent formula, the <b class="package">uexpr</b> package can calculate the volume in any desired units as long as h, w and l are all given in length units.</p> <p>Many common formulas use customary units. To use them with this package they need to be written in self consistent form. One simple way to do this is divide all variables in the expression by the units specified, then multiply the expression by the specified result units. For example a formula to calculate liquid flow through a valve for simple cases (no cavitation or flashing, low viscosity) is:</p> <pre class="doctools_example">Q = Cv*sqrt(DP/Sg)</pre> <p>where</p> <pre class="doctools_example">Q is volumetric flow rate in gpm</pre> <pre class="doctools_example">DP is pressure drop across the valve in psi</pre> <pre class="doctools_example">Sg is liquid specific gravity relative to water</pre> |
︙ | ︙ | |||
620 621 622 623 624 625 626 | and throat (d) diameter in inches, and flow rate (w) in lbm/hr (lbm is pounds mass, as opposed to lbf pounds force in <b class="package">uexpr</b>). Cd is a meter factor normally near 1.0. Y is a correction for compressibility of gases, use 1.0 for liquids or gases when the operating pressure is much higher that the pressure drop.</p> <pre class="doctools_example">w = 0.09970190*C.d*Y*d^2*sqrt((DP*den)/(1-(d/D)^4))</pre> <p>The self consistent version of this formula is:</p> | | | | 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 | and throat (d) diameter in inches, and flow rate (w) in lbm/hr (lbm is pounds mass, as opposed to lbf pounds force in <b class="package">uexpr</b>). Cd is a meter factor normally near 1.0. Y is a correction for compressibility of gases, use 1.0 for liquids or gases when the operating pressure is much higher that the pressure drop.</p> <pre class="doctools_example">w = 0.09970190*C.d*Y*d^2*sqrt((DP*den)/(1-(d/D)^4))</pre> <p>The self consistent version of this formula is:</p> <pre class="doctools_example">w = sqrt(2)*C.d*Y*pi/4*d^2*sqrt((DP*den)/(1-(d/D)^4))</pre> <p>the only vaguely mysterious number in the self consistent formula is sqrt(2). It will work with inputs and results in any compatible units. Meter inlet and throat size in any combination of ft, in, cm... DP could be given as psi, Pascal, bar, mm mercury ... Density (den) could be given as lbm/ft^3, gram/cm^3 ... The resulting mass flow rate can be lbm/hr gram/sec ...</p> <p>Often a formula for SI units will be self consistent, mass Kg, lengths meter, time sec, force newton (N), pressure Pascal (Pa) ... But it needs to be checked.</p> </div> <div id="subsection12" class="doctools_subsection"><h3><a name="subsection12">Fractional or Non-Integer Exponents</a></h3> <p>In practical formulas, fractional exponents on values with units rarely occur. Although mathematicians and physicists keep trying.</p> <p>The internal representation of a value with units is a list of double precision real numbers. The first entry is a magnitude, the others are exponents on the basis units. All the unit definitions (initially, and |
︙ | ︙ | |||
674 675 676 677 678 679 680 | is unitless, and there are no fractional exponents on the basis units.</p> </div> </div> <div id="keywords" class="doctools_section"><h2><a name="keywords">Keywords</a></h2> <p>Calculations, uexpr</p> </div> <div id="copyright" class="doctools_section"><h2><a name="copyright">Copyright</a></h2> | | | 707 708 709 710 711 712 713 714 715 716 | is unitless, and there are no fractional exponents on the basis units.</p> </div> </div> <div id="keywords" class="doctools_section"><h2><a name="keywords">Keywords</a></h2> <p>Calculations, uexpr</p> </div> <div id="copyright" class="doctools_section"><h2><a name="copyright">Copyright</a></h2> <p>Copyright © 2021-2024 J.D Bruchie (BSD License)</p> </div> </div></body></html> |
Changes to uexpr.n.
1 2 | '\" '\" Generated from file 'uexpr\&.dt' by tcllib/doctools with format 'nroff' | | | 1 2 3 4 5 6 7 8 9 10 | '\" '\" Generated from file 'uexpr\&.dt' by tcllib/doctools with format 'nroff' '\" Copyright (c) 2021-2024 J\&.D Bruchie (BSD License) '\" .TH "uexpr" n 0\&.1 uexpr "Calculations with Units" .\" The -*- nroff -*- definitions below are for supplemental macros used .\" in Tcl/Tk manual entries. .\" .\" .AP type name in/out ?indent? .\" Start paragraph describing an argument to a library procedure. |
︙ | ︙ | |||
286 287 288 289 290 291 292 | .sp \fBuexpr::unitNames\fR ?\fIglob pattern\fR? .sp \fBuexpr::unitsLike\fR ?\fIexpression\fR? .sp \fBuexpr::newUnit\fR \fIname\fR \fIexpression\fR .sp | | > > | 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 | .sp \fBuexpr::unitNames\fR ?\fIglob pattern\fR? .sp \fBuexpr::unitsLike\fR ?\fIexpression\fR? .sp \fBuexpr::newUnit\fR \fIname\fR \fIexpression\fR .sp \fBuexpr::func\fR \fIname\fR \fIparameter_names\fR \fIexpression\fR .sp \fBuexpr::uset\fR \fIname\fR \fIexpression\fR .sp .BE .SH DESCRIPTION Package \fBuexpr\fR provides commands to evaluate and format expressions that may include values with units\&. Its intended to be used as part of a program to document engineering, physical and mathematical calculations, so some things (like how negation "-" and |
︙ | ︙ | |||
318 319 320 321 322 323 324 | Expressions consist of operands and operators sometimes separated by spaces\&. .SH OPERANDS Operands can be numbers or numbers with units, sometimes saved in variables or a units dictionary\&. \fBuexpr\fR does not operate on strings\&. Comparisons in \fBuexpr\fR expressions return a numeric 1\&.0 for true, or 0\&.0 for false\&. .SS NUMBERS | | | 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 | Expressions consist of operands and operators sometimes separated by spaces\&. .SH OPERANDS Operands can be numbers or numbers with units, sometimes saved in variables or a units dictionary\&. \fBuexpr\fR does not operate on strings\&. Comparisons in \fBuexpr\fR expressions return a numeric 1\&.0 for true, or 0\&.0 for false\&. .SS NUMBERS Numbers start with a decimal point or digit, and may include only one decimal point\&. .CS 1 1\&.23 \&.123 .CE They may include a 10's exponent after an e or E\&. The exponent may be signed\&. .CS |
︙ | ︙ | |||
344 345 346 347 348 349 350 | 3e +5e == 3*e+5*e .CE .CS 3E+ 5e == 3*E+5*e .CE | < | | | 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 | 3e +5e == 3*e+5*e .CE .CS 3E+ 5e == 3*E+5*e .CE Here the space breaks up the scientific number definition, so both "e" and "E" are interpreted as variables or units\&. Keep this in mind if scientific notation is used in an expression and units or variables named "e or "E" exist\&. .SS "UNIT SPECIFIER" A unit specifier is a simple expression containing only multiply (including implied multiply) and divide operators, numbers, unit names and variable names\&. .CS |
︙ | ︙ | |||
376 377 378 379 380 381 382 | followed by (or multiplied by) a unit specifier\&. They are returned from \fBuexpr\fR, and can be saved in variables referenced by other \fBuexpr\fR expressions\&. .CS 100*BTU/hr ft^2 .CE | | | | | | | > > > > > > > | > | < < < < < < < < < < < < < < < < < < | > > > > > > > > > > > > > > | 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 | followed by (or multiplied by) a unit specifier\&. They are returned from \fBuexpr\fR, and can be saved in variables referenced by other \fBuexpr\fR expressions\&. .CS 100*BTU/hr ft^2 .CE defines a heat flux\&. .PP Internally the value with units is a list containing a magnitude and exponents on the basis units\&. For example using US Customary basis units: lbm, ft, sec, \&.\&.\&. .CS 3ft/sec .CE gives a list starting with 3 0 1 -1 \&.\&.\&. .CS 3m/sec .CE gives a list starting with 9\&.8425\&.\&.\&. 0 1 -1 \&.\&.\&. Note both values are speeds, and both have the same exponents on their basis units\&. Compatible units have identical exponents on the basis units, and can be added, subtracted and compared\&. .SS "NAMED VALUES" Named values can be variables from the context of the call to uexpr, or units or constants from the units dictionary\&. Names must start with a "_" or alphabetic character, and can contain "_" "\&." and alphanumeric characters\&. If the variable is processed by \fBuexpr\fR, it will be treated as a subexpression, and its result inserted into the expression being evaluated\&. This allows variables to hold values with units, which are returned from \fBuexpr\fR as simple expressions, not pure numbers\&. .SS VARIABLES If a name follows a "$" it is a reference to a variable in the context of the uexpr call or a local variable in a function definition\&. If a name does not follow a "$" it may still be a variable (see \fBName Resolution\fR)\&. If a variable name follows "$" the whole expression should be braced {}, to prevent Tcl from bypassing the variable handling by \fBuexpr\fR\&. .PP Unlike the rest of Tcl, variable names referenced using "$" can also include "_" and "\&." characters\&. See \fBName Resolution\fR\&. .PP Tcl array members (such as a(b3) can only be read using "$"\&. A reference without the "$" prefix will be interpreted as a function call, or an implied multiply of a variable by a subexpression\&. .SS "UNITS AND CONSTANTS" Units and constants are defined in a units dictionary, common to all uexpr calls in an interpreter\&. If a name follows a single ":" it will only be looked up in the units dictionary\&. If a unit or constant name does not follow ":", it may be interpreted as a reference to a variable, see \fBName Resolution\fR below\&. .CS :ft .CE look up the unit named ft in the units dictionary .SS "NAME RESOLUTION" If a name does not follow a "$" or ":" then the first value available from the following list is used: .IP \(bu If the name is in an expression defining a function (see \fBuexpr::func\fR) it is looked up in the function's parameter list .IP \(bu The name is looked up as a variable in the context of the uexpr call\&. .IP \(bu The name is looked up in the units dictionary\&. .IP \(bu An error occurs if no value was found\&. .PP If the name follows a "$" it is not looked up in the units dictionary\&. A name following a "$" can be a Tcl array member reference\&. .CS uexpr {3*$a(b)+1} .CE will multiply the value of the Tcl array "a" member "b" by 3 then add 1\&. Very similar to what Tcl \fBexpr\fR would do\&. Without the "$" uexpr will interpret (b) as a subexpression or possibly part of a function call\&. .CS uexpr {3*a(b)+1} .CE where a is a variable (and not a function name) multiplies 3, a and b, then adds 1 .SS "NAMED VALUES AND IMPLIED MULTIPLY" A number immediately followed by a named value will be interpreted as if there was an implied multiply between them\&. .CS 3ft+1inch .CE |
︙ | ︙ | |||
476 477 478 479 480 481 482 | will be interpreted as diam/(3*ft)\&. .SH OPERATORS \fBuexpr\fR does not deal with integers and bitwise operators (& | ~ << >>), or boolean operators (! && ||) or strings (eq ne lt le ge gt), or list searches (in ni)\&. Operators roughly in descending order of precedence: .IP \(bu | | | > > > > > > > | > | > | > > > > | | | | | > > > > > > > > > > > > > > > > > > > > > > > | | > > > > > | > > > > | > | | 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 | will be interpreted as diam/(3*ft)\&. .SH OPERATORS \fBuexpr\fR does not deal with integers and bitwise operators (& | ~ << >>), or boolean operators (! && ||) or strings (eq ne lt le ge gt), or list searches (in ni)\&. Operators roughly in descending order of precedence: .IP \(bu \&.\&. index into a list or nested lists in a variable\&. .CS set v {1 2 3 4} .CE .IP saves a list in the variable v .CS = v\&.\&.2 .CE .IP returns the 3rd list entry (3 in this case) note the list could have been generated using the \fBuexpr\fR "," operator too: .CS set v [= 1,2,3,4] .CE .IP To index into nested lists use a list of index values: .CS set m [= (1,2,3),(11,22,33),(111,222,333)] .CE .IP saves a 3x3 nested list in m .CS =m\&.\&.(2,0) .CE .IP returns a value of 111\&. .sp This is rather fragile\&. If the indexes do not match the actual nested list structure, there may not be any error, and odd results will be returned\&. .IP \(bu ** ^ exponentiation\&. The right argument (exponent) must be unitless\&. The left argument can have units\&. However if the exponent is not an integer, and the left operand has units, the result basis unit exponents may not be integers\&. In some cases this can cause problems\&. see \fBFractional or Non-Integer Exponents\fR\&. Note exponentiations group to the right so: .CS 2^3^2 == 2^(3^2) == 512 .CE .IP \(bu "implied multiply" multiplication is implied any time two values are not separated by an operator\&. A space between values is common, however a number followed by a named value also implies multiplication\&. Implied multiply has a higher precedence than multiply or divide\&. Useful in unit specifiers\&. .IP \(bu / divide, has same precedence as multiply\&. .IP \(bu * multiply, has same precedence as divide\&. .IP \(bu Unary + - operators to the left of their operand have a minimum precedence between addition or subtraction and multiplication\&. However their effective precedence is the maximum of their precedence and the precedence of the operator to their left (if any)\&. This means: .CS -2^2 == 0-2^2 == -4 .CE .IP This is not true for Tcl \fBexpr\fR, however it appears to match the conventions in "Handbook of Mathematical Functions" by Abramowitz and Stegun\&. See the definition of the error function for an example\&. .IP \(bu + - ++ -- addition and subtraction, left and right arguments must have compatible units\&. ++ and -- will also insert a line break in LaTeX formatted output\&. .IP \(bu < <= =< == != <> >= => > comparisons, return 1\&.0 for true, 0\&.0 for false\&. note ==, != and <> are not that useful with real numbers\&. Left and right arguments must have compatible units\&. .sp Comparisons can be chained\&. The result is true if all comparisons are true\&. .CS 7<a<13 .CE .IP will return true (1\&.0) if the value of a is between 7 and 13 .IP \(bu "," separate items in a list\&. Used to build lists and nested lists, such as lists of function arguments: .CS hypot(a,b) .CE .IP Also can be used to generate lists to use with the \&.\&. operator\&. For example coefficients for a polynomial using the sum function: .CS set C [uexpr 1,2,3,4] .CE .CS set x 0\&.1 .CE .CS uexpr sum(i,0,3,C\&.\&.i*x^i) .CE .IP will return 1\&.234 .IP \(bu "=" marks a result unit specifier for an expression\&. For example: .CS uexpr 15 lbf/3 in^2=psi .CE .IP would return "5\&.0 psi" Multiple result specifiers can be given: .CS uexpr::ltxExpr {15 N / 5 cm^2 = bar = Pa} .CE .IP would return results in bar and pascals\&. .PP "( )" group subexpressions (as for \fBexpr\fR)\&. .PP "%" is a constant defined as 0\&.01 in the units dict\&. .CS 45%*a = 0\&.45*a .CE .SH "MATH FUNCTIONS" Most math functions that work on real numbers in \fBexpr\fR are available in \fBuexpr\fR\&. However they are now unit aware\&. .IP \(bu Trigonometric Functions (sin cos tan) expect a single angle argument (deg rad \&.\&.\&.)\&.and return a pure number\&. .IP \(bu |
︙ | ︙ | |||
614 615 616 617 618 619 620 | .CS sum(i,0,4,2^i) .CE .IP will return 31\&.0 .sp | | | | | | 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 | .CS sum(i,0,4,2^i) .CE .IP will return 31\&.0 .sp A polynomial with coefficients in c .CS set c [= 1,2,3,4,5] .CE .CS set x 10 .CE .CS = sum(i,0,4,c\&.\&.i*x^i) .CE .IP will return 54321\&.0 .IP \(bu \fBhasUnits(expression)\fR returns 0 if the value is a pure number\&. .CS hasUnits(3) .CE .IP |
︙ | ︙ | |||
669 670 671 672 673 674 675 | then evaluates the expression and returns one result\&. If the expression ends with "=" followed by a unit specifier, the result is formatted to use those units\&. If the units specified do not match the actual result units, they will be multiplied or divided by enough base units to make them match\&. If multiple result unit specifiers are given only the last is used\&. If no result units are specified, the resulting unit expression will contain only base units\&. In any | | | 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 | then evaluates the expression and returns one result\&. If the expression ends with "=" followed by a unit specifier, the result is formatted to use those units\&. If the units specified do not match the actual result units, they will be multiplied or divided by enough base units to make them match\&. If multiple result unit specifiers are given only the last is used\&. If no result units are specified, the resulting unit expression will contain only base units\&. In any case the result can be used in an expression evaluated by uexpr\&. .sp .sp \fB[uexpr]\fR functions similarly to \fBexpr\fR with several significant differences\&. It is not a replacement for \fBexpr\fR\&. For example: .CS |
︙ | ︙ | |||
735 736 737 738 739 740 741 742 743 | Return all unit names if no pattern is given\&. .TP \fBuexpr::unitsLike\fR ?\fIexpression\fR? Returns a sorted list of all units and constants with the same dimensions (compatible units) as the result of the given expression\&. If no expression is given, all unitless constants are returned\&. .TP \fBuexpr::newUnit\fR \fIname\fR \fIexpression\fR | > > > > > > > > > > > > > > > | | | | | 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 | Return all unit names if no pattern is given\&. .TP \fBuexpr::unitsLike\fR ?\fIexpression\fR? Returns a sorted list of all units and constants with the same dimensions (compatible units) as the result of the given expression\&. If no expression is given, all unitless constants are returned\&. .sp For example if no new units are defined: .CS uexpr::unitsLike psi .CE .IP returns: atm bar Pa pascal psi Torr .sp .CS uexpr::unitsLike BTU/hr .CE .IP returns: hp watt .TP \fBuexpr::newUnit\fR \fIname\fR \fIexpression\fR \fBuexpr::newUnit\fR adds another unit to the unit dictionary\&. The expression defines the new unit in terms of any existing base or defined unit\&. The unit dictionary is common to all contexts within a Tcl interpreter\&. These changes are global\&. For example a mile is defined as: .CS uexpr::newUnit mile 5280*ft .CE .IP Note, units are very similar to variables, except: variables are looked up in the calling context of the expression evaluator\&. Unit definitions are global, available to any instance of uexpr in the same Tcl interpreter\&. .TP \fBuexpr::func\fR \fIname\fR \fIparameter_names\fR \fIexpression\fR \fBuexpr::func\fR adds a new function, usable in any uexpr call in the same interpreter\&. Variables in the expression that are not in the parameter list, have the value of the variable in the calling context at the time the function is defined\&. Variables in the parameter list have values set at the time the function is used\&. If not all the parameters are supplied in the function call, the value of a variable with the same name (in the calling context) is used (it came from |
︙ | ︙ | |||
811 812 813 814 815 816 817 818 819 820 821 822 823 824 | set c 33 uexpr ss(4,5) .CE .IP returns 12 (4+5+3), the change in c is ignored as it was not a function parameter .PP .SH "DIFFERENCES BETWEEN \FBUEXPR\FR AND \FBEXPR\FR" \fBuexpr\fR is not a replacement for \fBexpr\fR\&. .IP \(bu numbers are all double precision\&. Mostly because many unit conversion factors are not integers\&. .IP \(bu | > > > > > | | | | | 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 | set c 33 uexpr ss(4,5) .CE .IP returns 12 (4+5+3), the change in c is ignored as it was not a function parameter .TP \fBuexpr::uset\fR \fIname\fR \fIexpression\fR \fBuexpr::uset\fR writes the result of evaluating an expression to a variable, tcl array member or into a list or nested list saved in a variable\&. .PP .SH "DIFFERENCES BETWEEN \FBUEXPR\FR AND \FBEXPR\FR" \fBuexpr\fR is not a replacement for \fBexpr\fR\&. .IP \(bu numbers are all double precision\&. Mostly because many unit conversion factors are not integers\&. .IP \(bu Variable names without "$" or ":" prefixes will be looked up in the calling context or the units dictionary\&. .IP \(bu variables used in expressions can contain complete expressions, not just numbers\&. The variable contents will be evaluated and its value used in the expression\&. This will likely not work if Tcl gets to the variable first\&. Any expression that refers to variables with the "$" prefix should be within braces\&. .IP \(bu a variable can contain a list or nested lists of unit expressions\&. To get a value from a list, or nested list use the "\&.\&." operator\&. .IP \(bu There are no bit or logical operations (<< >> & | && ||)\&. |
︙ | ︙ | |||
851 852 853 854 855 856 857 | \fBuexpr::ltxExpr\fR 4*in+3*in=cm=ft .CE .IP \(bu There are no type conversion functions (double entier, wide)\&. int() exists, and returns a double with an integer value\&. .IP \(bu | | | | 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 | \fBuexpr::ltxExpr\fR 4*in+3*in=cm=ft .CE .IP \(bu There are no type conversion functions (double entier, wide)\&. int() exists, and returns a double with an integer value\&. .IP \(bu all functions available to uexpr and friends are in the ::uexpr::ufunc namespace\&. .IP \(bu When two values are not separated by an operator there is an implied multiply\&. Since variable names cannot start with a number or decimal point, "3a" will be interpreted as "3 a" or "5ft" as "5 ft"\&. Implied multiplies have higher precedence than normal multiply (*) or divide (/)\&. .CS |
︙ | ︙ | |||
953 954 955 956 957 958 959 | consistent formula, the \fBuexpr\fR package can calculate the volume in any desired units as long as h, w and l are all given in length units\&. .PP Many common formulas use customary units\&. To use them with this package they need to be written in self consistent form\&. One simple way to do this is divide all variables in the expression by the units | | | 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 | consistent formula, the \fBuexpr\fR package can calculate the volume in any desired units as long as h, w and l are all given in length units\&. .PP Many common formulas use customary units\&. To use them with this package they need to be written in self consistent form\&. One simple way to do this is divide all variables in the expression by the units specified, then multiply the expression by the specified result units\&. For example a formula to calculate liquid flow through a valve for simple cases (no cavitation or flashing, low viscosity) is: .CS Q = Cv*sqrt(DP/Sg) .CE where |
︙ | ︙ | |||
1016 1017 1018 1019 1020 1021 1022 | .CS w = 0\&.09970190*C\&.d*Y*d^2*sqrt((DP*den)/(1-(d/D)^4)) .CE The self consistent version of this formula is: .CS | | | | 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 | .CS w = 0\&.09970190*C\&.d*Y*d^2*sqrt((DP*den)/(1-(d/D)^4)) .CE The self consistent version of this formula is: .CS w = sqrt(2)*C\&.d*Y*pi/4*d^2*sqrt((DP*den)/(1-(d/D)^4)) .CE the only vaguely mysterious number in the self consistent formula is sqrt(2)\&. It will work with inputs and results in any compatible units\&. Meter inlet and throat size in any combination of ft, in, cm\&.\&.\&. DP could be given as psi, Pascal, bar, mm mercury \&.\&.\&. Density (den) could be given as lbm/ft^3, gram/cm^3 \&.\&.\&. The resulting mass flow rate can be lbm/hr gram/sec \&.\&.\&. .PP Often a formula for SI units will be self consistent, mass Kg, lengths meter, time sec, force newton (N), pressure Pascal (Pa) \&.\&.\&. But it needs to be checked\&. .SS "FRACTIONAL OR NON-INTEGER EXPONENTS" In practical formulas, fractional exponents on values with units rarely occur\&. Although mathematicians and physicists keep trying\&. .PP The internal representation of a value with units is a list of double precision real numbers\&. The first entry is a magnitude, the others are exponents on the basis units\&. All the unit definitions (initially, and |
︙ | ︙ | |||
1076 1077 1078 1079 1080 1081 1082 | polytropic process, where volume ratios are raised to powers between 1\&.4 and 1\&.1 (isentropic volume exponent)\&. Once again the volume ratio is unitless, and there are no fractional exponents on the basis units\&. .SH KEYWORDS Calculations, uexpr .SH COPYRIGHT .nf | | | 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 | polytropic process, where volume ratios are raised to powers between 1\&.4 and 1\&.1 (isentropic volume exponent)\&. Once again the volume ratio is unitless, and there are no fractional exponents on the basis units\&. .SH KEYWORDS Calculations, uexpr .SH COPYRIGHT .nf Copyright (c) 2021-2024 J\&.D Bruchie (BSD License) .fi |
Changes to uexpr.tcl.
︙ | ︙ | |||
9 10 11 12 13 14 15 | # Internally a value with units (uval) is a list, # the first element is a magnitude, # the rest are exponents on the base dimensions: # Length Mass Time Charge Degree ... # # A uvalue is string representation of a uval # It is a more readable expression starting with a number | | | | < | | < > | 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | # Internally a value with units (uval) is a list, # the first element is a magnitude, # the rest are exponents on the base dimensions: # Length Mass Time Charge Degree ... # # A uvalue is string representation of a uval # It is a more readable expression starting with a number # multiplied and divded by units (from the units dictionary or named variables). # Results from uexpr are returned as uvalues. # # currently lists or nested lists of uvalues can be indexed into # they are vaguely like vectors or arrays # Ideas for vector and array calculations (not implemented): # arrays are a list of: # the array depth (0=uval, 1=vector, 2=matrix ...) # the nested list of uvals # alternate could be nested list of values with shared units # gives faster but less general math # a matrix (2d array) is a list of rows # (compatable-ish with struct::matrix serailze) |
︙ | ︙ | |||
67 68 69 70 71 72 73 74 75 76 77 78 79 80 | # otherwise error occurs (incompatible array shapes) # allows easy biasing of vector or matrix # could also: # add identity value to shorter value # (0 for add/sub, 1 for multiply/divide...) # repeat the shorter value list as needed (use case ??) # # Results are converted back to a simple unit expression string like "1 ft" # # basic math functions are: # # add (+) subtract (-) -- error if dimensions do not match, operate on value # mul (* or space) divide (/) -- add/subtract dimensions, operate on value # power (^ or **) -- operate on value, multiply dimensions by exponent | > > > > > > > > | 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | # otherwise error occurs (incompatible array shapes) # allows easy biasing of vector or matrix # could also: # add identity value to shorter value # (0 for add/sub, 1 for multiply/divide...) # repeat the shorter value list as needed (use case ??) # # vector [list of uexprs] and matrix (list of lists of uexprs) # name..<index expr> # index expr can be a number, a variable, or (<index expr>,<index expr, ...) # first access sets number of dimensions # use \U21BC (leftward harpoon with barb upwards) as operator symbol # Results are converted back to a simple unit expression string like "1 ft" # # basic math functions are: # # add (+) subtract (-) -- error if dimensions do not match, operate on value # mul (* or space) divide (/) -- add/subtract dimensions, operate on value # power (^ or **) -- operate on value, multiply dimensions by exponent |
︙ | ︙ | |||
112 113 114 115 116 117 118 | # uexpr "10 gal = in^2" # returns: # 192.499...997 ft * in^2 # # operators specific to uexpr: # $ following name is a variable in the calling context # : following name is a unit | | | | | | | | < < | > | | | | > | > > > > < < < | | < < < < < < < | 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 | # uexpr "10 gal = in^2" # returns: # 192.499...997 ft * in^2 # # operators specific to uexpr: # $ following name is a variable in the calling context # : following name is a unit # .. separates a variable containing a list or nested lists from an index expression # an index expression can be a single value v..3 # or a list, such as a..(i,j) or a..(1,i,2) # ++ is a line break in an equation at a + operator # it is converted to \U229E (boxed +) then parsed to +CR: # if left op is +CR, return left op with + and right op appended # otherwise return {+CR leftOp + rightOp} # +CR evaluates to sum the values from its list (ignoring the +/- operators) # +CR generates LaTeX for an array with one operand per line, # separated by the given + or - operators # -- is a line break at a subtract # it is mapped to \U229F (boxed -) and \U2296 (circled -) # when \U229F is parsed: # if left op is +CR, append "-" and right op to left op operand list and return it. # otherwise return {+CR leftOp - rightOp} # \U2296 (circled -) is parsed to an invisible negate (ineg) # ineg negates its operand during evaluation, # but generates no LaTeX output # Precedence is higher than +/- but lower than negate or multiply/divide # It is used to capture and negate the first +/- precedence operand after a -- operator # so the +CR operator can just add its operands during evaluation. # # all numbers are real because: # unit conversions can force numbers to be real, # so for consistancy, all are real # real numbers can be: 1.23e3 1.23*10^3 or 1230 # # base unts can be US customary {lbm ft sec Coul degR deg lbmole) # or metric {gm m sec Coul degK deg gmole) # or any other self consistant set (see newBaseUnit and uBaseNames) # # names (variables or functions) can include symbols and subscripts # (and unicode) # They cannot start with a diget or ".", but can contain them. # _ followed by a letter selects a symbol, typically a greek char # (in LaTeX output). # . indicates any following chars are part of a subscript (in LaTeX output). # variables are looked up in the following sequence: # as a local variable (in locals dict) # in the calling contex as a variable # finally as a unit (in the units dictionary) # variable names preceded by "$" are only looked up in the calling context # (brace {} the expression). For example uexpr {3*$x} # variable names preceeded by ":" are only looked up in the units dictionary, # for example: # uexpr {5:ft + 3:in} # for example: _r.0 could be: # a local variable _r.0 (from a function parameter list) # a variable in the calling context _r.0 # or a unit _r.0 # LaTeX generated for: _r.0 would be: \rho _\mathrm{0} # Most math functions defined by Tcl expr are defined here to operate with # unit values. # Trig functions expect units of angle (deg, rad) # Some functions expect unitless values (asin() acos() ...) # Some accept any units (sqrt() abs() ...) # # Additional Functions can be defined (or existing functions redefined) |
︙ | ︙ | |||
276 277 278 279 280 281 282 | # If the operator left prec is higher, it parses a right value # (at its right precedence). # Then It returns: # a new value (including itself as operator and its operands), # and its left precedence. # Its token has been used. | | | | 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 | # If the operator left prec is higher, it parses a right value # (at its right precedence). # Then It returns: # a new value (including itself as operator and its operands), # and its left precedence. # Its token has been used. # When parsing tokens as values: # They get a precedence, and return a value at that precedence. # In most cases applyOp is used to apply any further operators # to the right of the value with left precedence higher than the given precedence. # "(" and ")" can appear as value or operator tokens (see details below) package provide uexpr 0.1 namespace eval ::uexpr { |
︙ | ︙ | |||
339 340 341 342 343 344 345 346 347 348 349 350 351 | } # information common to all instances of uexpr variable sys # map multichar and annoying symbols (: to right arrow) to single char # trying to define a proc named : in a namespace # does not seem to work with ensembles set sys(symmap) [list "==" \U2248 \ "!=" \U2249 "<>" \U2249 \ "<=" \U2264 "=<" \U2264 \ ">=" \U2265 "=>" \U2265 \ ** ^ \ ++ \U229E \ | > | | | 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 | } # information common to all instances of uexpr variable sys # map multichar and annoying symbols (: to right arrow) to single char # trying to define a proc named : in a namespace # does not seem to work with ensembles # note -- maps to two operator chars (boxed- and circled -) set sys(symmap) [list "==" \U2248 \ "!=" \U2249 "<>" \U2249 \ "<=" \U2264 "=<" \U2264 \ ">=" \U2265 "=>" \U2265 \ ** ^ \ ++ \U229E \ -- \U229F\U2296 \ : \U2192 \ .. \U21BC] # operator symbol chars (and %) set sys(opchar) " \n\r\t()$\U2192\U21BC%^*/<>=+,-\U2248\U2249\U2264\U2265\U2296\U229E\U229F" # nested list operators that have only literal operands (no further nesting) # uval only occurs within user defined functions set sys(literal) [list num name varName unitName funcName uval] # utility and debugging stuff |
︙ | ︙ | |||
431 432 433 434 435 436 437 | } proc tokenize {str} { variable sys | | | 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 | } proc tokenize {str} { variable sys # merge multi char symbols into a single char (2 different chars for -- operator) # break around any recognized operator/function character, # "%" or white space set words [spliti [string map $sys(symmap) $str] $sys(opchar)] dm {tokenize words $words} # check for special cases where words start with a number |
︙ | ︙ | |||
508 509 510 511 512 513 514 515 516 517 518 519 520 521 | [string range $word $ii end]] # skip new list entry incr n } # continue search at word after the one just processed incr n } # return list after removing any remaining blank or empty strings lmap word $words { if {[string is space $word]} {continue} set word } } | > > > > > > > > > > > > > > > > > > > | 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 | [string range $word $ii end]] # skip new list entry incr n } # continue search at word after the one just processed incr n } # look for tcl array references in list: $ <name> ( # with no spaces between elements # collapse the name and everything between the following ( and next ) # into the name as an array member reference. # Note there can be any number of "(" and spaces in a member name. # This appears to match how Tcl (and Tcl expr) parses array member names set i -2 while {-1 != [set i [lsearch -start $i+2 -exact $words "$"]]} { # if this is a reference to a Tcl array variable member if {"(" eq [lindex $words $i+2]} { # everything up to and including the next ) is part of the array reference set ii [lsearch -start $i+3 -exact $words ")"] if {-1 == $ii} { error "[join [lrange $words $i end] {}] is not a valid Tcl array reference" } set words [lreplace $words $i+1 $ii [join [lrange $words $i+1 $ii] ""]] } } # return list after removing any remaining blank or empty strings lmap word $words { if {[string is space $word]} {continue} set word } } |
︙ | ︙ | |||
534 535 536 537 538 539 540 | # perhaps rename lvl to parserContext ??? set tokens [tokenize $str] dm {parse $tokens} # index to last token used set n -1 set lvl #[info level] | | | 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 | # perhaps rename lvl to parserContext ??? set tokens [tokenize $str] dm {parse $tokens} # index to last token used set n -1 set lvl #[info level] # get first value in the expression, then apply it to the following operators applyOp $lvl 1 [parseValue [nextToken $lvl] $lvl 1] } # tokens are kept in a variable (tokens) in the context of the parse command # During processing n has the index of the last token processed # It is also in the context of the parsing command |
︙ | ︙ | |||
616 617 618 619 620 621 622 | setOp > 20 21 # generic chained compare operator setOp CC 20 21 setOp + 30 31 setOp - 30 31 setOp +CR 28 29 | | | | 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 | setOp > 20 21 # generic chained compare operator setOp CC 20 21 setOp + 30 31 setOp - 30 31 setOp +CR 28 29 # invisible negate (part of -- operator) setOp ineg "" 33 # TCL and excel... puts negation higher precedence than # any other inline binary operator # in particular: = 2^-3*5 # would be 2^-15 with prec = 21 # should be 0.625 or 5/8 # however -2^2 = 4 |
︙ | ︙ | |||
718 719 720 721 722 723 724 725 726 727 728 729 730 731 | neg \ [parseValue [nextToken $lvl] \ $lvl \ [::tcl::mathfunc::max $prec $rprec(neg)]]] } proc parseValue::+ {lvl prec} { # unary + variable rprec # get value, but with adjusted precedence # currently not adding a {pos ...} wrapper tailcall parseValue \ [nextToken $lvl] \ | > > > > > > > > > > > > > > > > | 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 | neg \ [parseValue [nextToken $lvl] \ $lvl \ [::tcl::mathfunc::max $prec $rprec(neg)]]] } # circled - proc parseValue::\U2296 {lvl prec} { # invisible negate (at slightly above +/- precedence) # used as part of -- # fairly generic for any operator to left of its operand # however there is only invisible negate for now variable rprec applyOp $lvl $prec \ [list \ ineg \ [parseValue [nextToken $lvl] $lvl $rprec(neg)]] } proc parseValue::+ {lvl prec} { # unary + variable rprec # get value, but with adjusted precedence # currently not adding a {pos ...} wrapper tailcall parseValue \ [nextToken $lvl] \ |
︙ | ︙ | |||
752 753 754 755 756 757 758 | error "no matching ')'" } # apply value to trailing operators of higher precedence applyOp $lvl $prec $val } | | > | | | 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 | error "no matching ')'" } # apply value to trailing operators of higher precedence applyOp $lvl $prec $val } # following name is a Tcl variable name # Note $ does not have an entry in rprec or lprec # because it always gets just the next token # Much of the parsing work is done in [tokenize] proc parseValue::\$ {lvl prec} { set name [nextToken $lvl] set char1 [string index $name 0] # check that the following string can be a valid variable # (do not check that it exists) # apply the variable to the next operator (if its precedence is higher) applyOp $lvl $prec \ [if {[string is alpha -strict $char1] || [string match "_" $char1]} { # valid variable name list varName $name } else { error "$name is not a valid variable name" }] } # following name is a unit # : is mapped to unicode right arrow (\U2192) # Note : (\U2192) does not have an entry in rprec or lprec # because it always gets the next token proc parseValue::\U2192 {lvl prec} { set name [nextToken $lvl] set char1 [string index $name 0] # check that the following string can be a valid unit # (do not check that it exists) # apply the unit to the next operator (if its precedence is higher) |
︙ | ︙ | |||
796 797 798 799 800 801 802 | }] } proc parseValue::\) {lvl prec} { # ) is normally an operator, # however this could be the end of a function call with no operands | < > | 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 | }] } proc parseValue::\) {lvl prec} { # ) is normally an operator, # however this could be the end of a function call with no operands variable rprec # if not within a function call if {$prec != $rprec(\()} { # an empty last value in a list (1,) will cause an error here !!! # which is okay for now error "not within function call" } # always restore token to match normal state at end of function parsing reuseToken $lvl |
︙ | ︙ | |||
907 908 909 910 911 912 913 | variable lprec dm {parseChainedCompare $op $lvl prec: $prec lvalue:$lvalue} # if operator left prec is greater than or equal to subexpression prec if {$prec <= $lprec(CC)} { # if left operand is CC if {"CC" eq [lindex $lvalue 0]} { # add to existing CC list | | | 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 | variable lprec dm {parseChainedCompare $op $lvl prec: $prec lvalue:$lvalue} # if operator left prec is greater than or equal to subexpression prec if {$prec <= $lprec(CC)} { # if left operand is CC if {"CC" eq [lindex $lvalue 0]} { # add to existing CC list lappend lvalue [list funcName $op] [parseValue [nextToken $lvl] $lvl $rprec(CC)] } else { # apply CC (and its operator and right subexpression value) # to the current subexpression set lvalue [list \ CC \ $lvalue \ [list funcName $op] \ |
︙ | ︙ | |||
935 936 937 938 939 940 941 942 943 944 945 946 947 948 | interp alias {} parseOp::\U2249 {} ::uexpr::parseChainedCompare \U2249 interp alias {} parseOp::< {} ::uexpr::parseChainedCompare < interp alias {} parseOp::\U2264 {} ::uexpr::parseChainedCompare \U2264 interp alias {} parseOp::\U2265 {} ::uexpr::parseChainedCompare \U2265 interp alias {} parseOp::> {} ::uexpr::parseChainedCompare > proc parse+CR {op lvl prec lvalue} { # line break at + or - # leaves a +CR command followed by first left op # then pairs of operatot (=/-) and right operand. For example # 3 ++ 4 -- 5 # yields # +cr 3 + 4 - 5 | > > | 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 | interp alias {} parseOp::\U2249 {} ::uexpr::parseChainedCompare \U2249 interp alias {} parseOp::< {} ::uexpr::parseChainedCompare < interp alias {} parseOp::\U2264 {} ::uexpr::parseChainedCompare \U2264 interp alias {} parseOp::\U2265 {} ::uexpr::parseChainedCompare \U2265 interp alias {} parseOp::> {} ::uexpr::parseChainedCompare > # !!! needss work -- causes whole following block to be subtracted # see test +CR-11 proc parse+CR {op lvl prec lvalue} { # line break at + or - # leaves a +CR command followed by first left op # then pairs of operatot (=/-) and right operand. For example # 3 ++ 4 -- 5 # yields # +cr 3 + 4 - 5 |
︙ | ︙ | |||
1217 1218 1219 1220 1221 1222 1223 | dm {parseOp::arrayAccess $lvl prec: $prec lvalue:$lvalue} # arrayAccess left prec is always greater than left subexpression prec if {$prec > $lprec(arrayAccess)} { error "left prec of array access ($lprec(arrayAccess)) \ should always be larger than $prec" } # verify left subexpr is a name | | > | 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 | dm {parseOp::arrayAccess $lvl prec: $prec lvalue:$lvalue} # arrayAccess left prec is always greater than left subexpression prec if {$prec > $lprec(arrayAccess)} { error "left prec of array access ($lprec(arrayAccess)) \ should always be larger than $prec" } # verify left subexpr is a name if {[lindex $lvalue 0] ni {name varName}} { #puts "lvalue $lvalue" error "parsing arrayAccess expects a name to left, not $lvalue" } # get right value set rvalue [parseValue [nextToken $lvl] $lvl $rprec(\U21BC)] # if right value is not a list subexpr, make it one if {"subexpr" eq [lindex $rvalue 0]} { if {"vlist" eq [lindex $rvalue 1 0]} { |
︙ | ︙ | |||
1936 1937 1938 1939 1940 1941 1942 | # generally eval and ltx versions are next to each other # to insure consistancy # name -- evaluate anything not an operator or a number # First searches for a local variable (returns uval from locals dict # in uExpr context) | | < < | > < < < | > < < < < < | | < < < > < < < < < < < < < < < < < < < | 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 | # generally eval and ltx versions are next to each other # to insure consistancy # name -- evaluate anything not an operator or a number # First searches for a local variable (returns uval from locals dict # in uExpr context) # then as a variable in the calling context # if a variable is not found, look for a unit with the given name # In any case, the value is an expression which is parsed and evaluated # to return a uval # Elsewhere can only search for variable names ("$" varName) or units (":") # note Tcl array members can only be referenced using $ # A name can also be a function name in the nested list. # In that case this function is not executed. # For LaTeX output only the variable name is used proc eval::name {name} { dm {name $name level: [info level]} # 2 levels to calling proc for context # one level for this proc, one for namespace eval upvar 2 callingLevel callingLevel locals locals dm {in name $name callingLevel=$callingLevel} # if its a function local variable name if {[info exists locals] && [dict exists $locals $name]} { return [dict get $locals $name] # if its a variable name in the calling scope } elseif {![catch [list upset $callingLevel $name] value]} { # convert to a unit list value dm {variable name=$name val=$value} return [namespace eval ::uexpr::eval [tclizeExpr $value]] # if its a unit name } elseif {0 == [catch {set ::uexpr::units($name)} value]} { dm {unit name=$name val=$value} return $value } else { error "uExpr: $name is not a variable or unit name" } } proc eval::varName {name} { # same as name, except without checking for a unit value dm {name $name level: [info level]} # 2 levels to calling proc, # one level for this proc, one for namespace eval upvar 2 callingLevel callingLevel locals locals dm {in name $name callingLevel=$callingLevel} # if it is a local variable if {[dict exists $locals $name]} { return [dict get $locals $name] # if its a variable name in the calling scope } elseif {![catch [list upset $callingLevel $name] value]} { # convert to a unit list value dm {variable name=$name val=$value} return [namespace eval ::uexpr::eval [tclizeExpr $value]] } else { error "uExpr: $name is not a variable name" } } proc eval::setName {name value} { # searches for variable in same way as varName # (without checking for a unit value) # an undefined variable is defined in the callingLevel context # saves value in variable refered to dm {name $name level: [info level]} # 2 levels to calling proc, # one level for this proc, one for namespace eval upvar 2 callingLevel callingLevel locals locals lvl lvl dm {in name $name callingLevel=$callingLevel} # if its a variable name in a function context # note local vars are all defined before first reference if {[dict exists $locals $name]} { dict set locals $name $value } else { uplevel $callingLevel [list set $name $value] } return $value } |
︙ | ︙ | |||
2070 2071 2072 2073 2074 2075 2076 | proc eval::arrayAccess {name indexList} { # save value in named variable dm {arrayAccess $name level: [info level]} # 2 levels to calling proc, # one level for this proc, one for namespace eval upvar 2 callingLevel callingLevel locals locals lvl lvl dm {in arrayAccess $name $indexList callingLevel=$callingLevel} | < < < | < < < < < < < | < < < < < < < < < < < < | 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 | proc eval::arrayAccess {name indexList} { # save value in named variable dm {arrayAccess $name level: [info level]} # 2 levels to calling proc, # one level for this proc, one for namespace eval upvar 2 callingLevel callingLevel locals locals lvl lvl dm {in arrayAccess $name $indexList callingLevel=$callingLevel} # if its a function local variable if {[info exists locals] && [dict exists $locals $name]} { set val [lindex [dict get $locals $name] {*}$indexList] if {"" eq $val} { error "uExpr: index $indexList out of range for array $name" } return $val # if its a variable name in the calling scope } elseif {[catch [list upset $callingLevel $name] value]} { error "uExpr: $name with index $indexList\ is not an array variable name" } set val [lindex $value {*}$indexList] if {"" eq $val} { error "uExpr: array $name has no value for index $indexList" } # get uval from expression in array variable namespace eval ::uexpr::eval [tclizeExpr $val] } proc eval::setArrayAccess {name indexList value} { # save value in nested list in named variable dm {arrayAccess $name level: [info level]} # 2 levels to calling proc # one level for this proc, one for namespace eval upvar 2 callingLevel callingLevel locals locals lvl lvl dm {in arrayAccess $name $indexList callingLevel=$callingLevel} # if its a variable name in a local variable in a function if {[info exists locals] && [dict exists $locals $name]} { set val [dict set $locals $name] if {[catch {lset val {*}$indexList $value}]} { error "cannot set list in local variable $name\ at index [join $indexList ,]" } dict set locals $name $val # if its a variable name in the calling context } elseif {[uplevel $callingLevel [list info exists $name]]} { if {[catch {uplevel $callingLevel \ [concat \ [list lset $name] \ $indexList \ [list $value]]}]} { error "cannot set list entry at index [join $indexList ,]\ in variable $name" } } else { # variable does not exist # save value, to be returned set val $value foreach i $indexList { # index list must be all 0's if {0 != $i} { |
︙ | ︙ | |||
2193 2194 2195 2196 2197 2198 2199 | proc eval::vlist {args} { # returns a list of uvals (list of lists) return $args } proc ltx::vlist {args} { | | | 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 | proc eval::vlist {args} { # returns a list of uvals (list of lists) return $args } proc ltx::vlist {args} { join $args {, } } # map symbol char to LaTeX # !! check against LaTeX set ltx::syms [dict create \ |
︙ | ︙ | |||
2370 2371 2372 2373 2374 2375 2376 2377 2378 2379 2380 2381 2382 2383 2384 2385 2386 2387 2388 2389 | # procs for each operator to generate a value in eval namespace # procs to generate LaTeX in ltx namespace proc eval::neg {u} { dm neg lreplace $u 0 0 [expr -[lindex $u 0]] } proc ltx::neg {u} { string cat "-" $u } proc ltx::BinaryOp {op args} { lassign $args a b string cat $a $op $b } | > > > > > > > > > > | 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 | # procs for each operator to generate a value in eval namespace # procs to generate LaTeX in ltx namespace proc eval::neg {u} { dm neg lreplace $u 0 0 [expr -[lindex $u 0]] } proc ltx::neg {u} { string cat "-" $u } # invisible negate (part of -- operator) interp alias {} eval::ineg {} ::uexpr::eval::neg proc ltx::ineg {u} { # the latex code is handled by \U229F (via +CR -) return $u } proc ltx::BinaryOp {op args} { lassign $args a b string cat $a $op $b } |
︙ | ︙ | |||
2506 2507 2508 2509 2510 2511 2512 2513 | interp alias {} ltx::+ {} ::uexpr::ltx::BinaryOp + proc eval::+CR {a args} { # multiple add/subtract operations # a is the first (left) operand # args is a list of +/- uval pairs foreach {op b} $args { | > > | | | 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 | interp alias {} ltx::+ {} ::uexpr::ltx::BinaryOp + proc eval::+CR {a args} { # multiple add/subtract operations # a is the first (left) operand # args is a list of +/- uval pairs # always evaluates as + # subtraction is (-- \U229F) is handled by \U2296 and ine foreach {op b} $args { set a [+ $a $b] } return $a } # LaTeX for a line break in an equation at an inline binary +/- operator proc ltx::+CR {a args} { # build a vertical array of subexpressions # using operators between subexpressions # append operator to the left subexpression, # prepend operator to the right subexpression # start array with first subexpression set res [string cat { \begin{array}{c} } $a] foreach {op b} $args { # continue by appending operator, line break # operator (again) followed by next subexpression set res [string cat $res " " $op { \\ \mbox{} } $op " " $b] } # end array string cat $res { \end{array} } |
︙ | ︙ | |||
2770 2771 2772 2773 2774 2775 2776 | # parse, tclize, memoize an expression # memoize expressions in uexpr::exprs dict # Note when the resulting cmd is evaluated to a value it expects: # callingLevel (context for variables) # and locals (a dict of local variable values from a function call, often empty) # to be defined in the context of the namespace eval eval ... variable exprs | < > > | 2776 2777 2778 2779 2780 2781 2782 2783 2784 2785 2786 2787 2788 2789 2790 2791 2792 2793 | # parse, tclize, memoize an expression # memoize expressions in uexpr::exprs dict # Note when the resulting cmd is evaluated to a value it expects: # callingLevel (context for variables) # and locals (a dict of local variable values from a function call, often empty) # to be defined in the context of the namespace eval eval ... variable exprs variable debug # disable memoizing if debug is set if {!$debug && [dict exists $exprs $expr]} { # return memorized parsed and tclized expression dict get $exprs $expr } else { set cmd [tclize [parse $expr]] dict set exprs $expr $cmd return $cmd } } |
︙ | ︙ | |||
3049 3050 3051 3052 3053 3054 3055 | $callingLevel \ [namespace eval eval [tclizeExpr $expr]] \ [lindex $runits end] } | | | | < < < | < < < < < < < < < | > | | | 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 | $callingLevel \ [namespace eval eval [tclizeExpr $expr]] \ [lindex $runits end] } # saves a value in a variable in the calling context # The variable name is formatted the same as variable references in uexpr # The variable can be a reference to a tcl array: a(b) # It can be a reference to an entry in vector or higher dimension array # such as: vv..1 or aa..(1,3,2) # Return the value saved proc uset {varExpr value} { # evaluate an expression # return result as valid unit expression set callingLevel #[expr {[info level] -1}] # there are no local variables set locals {} # parse variable reference set ve [parse \$$varExpr] #puts "ve: $ve" # make primary variable reference a set referenced value switch [lindex $ve 0] { name {error "oops make this a dash"} varName { lset ve 0 setName lset ve 1 [list uval [lindex $ve 1]] } arrayAccess {lset ve 0 setArrayAccess} default { error "$varExpr is not a variable reference" } } #puts "uset ve: $ve value: $val" namespace eval eval [concat [tclize $ve] [list $value]] } # format expression and result with LaTeX # return a list of: # LaTeX formatted expression |
︙ | ︙ | |||
3206 3207 3208 3209 3210 3211 3212 | variable units lsort -dictionary [array names units $pattern] } | | > | 3202 3203 3204 3205 3206 3207 3208 3209 3210 3211 3212 3213 3214 3215 3216 3217 3218 3219 3220 3221 3222 | variable units lsort -dictionary [array names units $pattern] } proc unitsLike {{expr 1} args} { # return a sorted list of all units and constants # with the same dimensions as the expression variable units set callingLevel [expr {[info level] - 1}] set locals {} set expr [concat $expr $args] set uval [namespace eval eval [tclizeExpr $expr]] lsort -dictionary [lmap {key value} [array get units] { if {[SameUnits $uval $value]} { set key } else { continue } |
︙ | ︙ |
Changes to uexpr.test.
︙ | ︙ | |||
133 134 135 136 137 138 139 | } -match glob -returnCodes 1 \ -result "*expects two or three arguments: stack-level variable-name ?value?*" test uset-1 {write to new variable} -setup { catch {unset a} } -body { | | | | | < | | | | | | < | | | | 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 | } -match glob -returnCodes 1 \ -result "*expects two or three arguments: stack-level variable-name ?value?*" test uset-1 {write to new variable} -setup { catch {unset a} } -body { expr {[uexpr::uset a 34*5psi] == $a} } -result 1 test uset-1.1 {write to existing variable} -setup { catch {unset a} set a 6 } -body { expr {[uexpr::uset a 34*5psi] == $a} } -result 1 test uset-2 {write where Tcl array exists but member does not} -setup { catch {unset a} set a(1) 4 } -body { expr {[uexpr::uset a(3) 34*psi] == ${a(3)}} } -result 1 test uset-2.1 {write where array and member exist} -setup { catch {unset a} set a(3) 4 } -body { expr {[uexpr::uset a(3) 34*psi] == $a(3)} } -result 1 test uset-3 {write into new list} -setup { catch {unset a} } -body { expr {[uexpr::uset a..0 34] == [lindex $a 0]} } -result 1 test uset-3.1 {write into new list} -setup { catch {unset a} } -body { expr {[uexpr::uset a..(0,0) 34psi] == [lindex $a 0 0]} } -result 1 test uset-3 {write into new list} -setup { catch {unset a} } -body { expr {[uexpr::uset a..0 34] == [lindex $a 0]} } -result 1 test uset-3.1 {write into existing list} -setup { catch {unset a} set a [uexpr 1psi,2ft,3amp] } -body { expr {[uexpr::uset a..1 34*5psi=psi] == [lindex $a 1]} } -result 1 test uset-4 {append to existing list} -setup { catch {unset a} set a [uexpr 1psi,2ft,3amp] } -body { expr {[uexpr::uset a..3 34*5psi] == [lindex $a 3]} } -result 1 test uset-5 {append to existing value, creating list} -setup { catch {unset a} set a [uexpr 1psi] } -body { expr {[uexpr::uset a..1 34*5psi] == [lindex $a 1]} } -result 1 test uset-6 {not a writable reference} -setup { set a [uexpr 1psi] } -body { uexpr::uset 3+4 34psi } -returnCodes error -match glob -result "* is not a valid variable name*" test uset-6.1 {index too big} -setup { set a [list 1 2 ] } -body { uexpr::uset a..3 66 } -returnCodes error -match glob \ -result "cannot set list entry at index 3 in variable a" test uset-6.2 {bad index for list in array member} -setup { catch {unset a} set a(m) [list 1 2] } -body { uexpr::uset a(m)..4 34*5psi } -returnCodes error -match glob \ -result {*cannot set list entry at index 4 in variable a(m)*} test uset-6.3 {bad index for first write to list} -setup { catch {unset a} } -body { uexpr::uset a..4 34*5psi } -returnCodes error -match glob \ -result "*cannot use index 4 for first write to list in a*" test ltx::num-1 {} -setup $SETUP -body { uexpr::ltx::num 1.0 |
︙ | ︙ | |||
396 397 398 399 400 401 402 | uexpr {3*$a=in} } -cleanup {unset a} -result {9.0 in} test varName-2 {} -setup {catch {unset a}} -body { uexpr {3*$a=in} } -returnCodes error -match glob -result {*a is not a variable name*} | > > > | > > > > | > | | | | > > > > > > > > > > > | 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 | uexpr {3*$a=in} } -cleanup {unset a} -result {9.0 in} test varName-2 {} -setup {catch {unset a}} -body { uexpr {3*$a=in} } -returnCodes error -match glob -result {*a is not a variable name*} test varName-3 {tcl var ignores unit} -setup {set ft 5sec} -body { uexpr {3*$ft=sec} } -cleanup {unset ft} -result {15.0 sec} test varName-3.1 {tcl var ignores unit} -setup {catch {unset ft}} -body { uexpr {3*$ft=in} } -returnCodes error -match glob -result {*ft is not a variable name*} test varName-4 {use . _ chars in Tcl var name} -setup {set _a.b 5} -body { uexpr {3+$_a.b} } -cleanup {unset _a.b} -result 8 test varName-4.1 {verify braces required} -setup {set _a.b 5} -body { uexpr 3+$_a.b } -cleanup {unset _a.b} -returnCodes error -match glob \ -result {*can't read "_a": no such variable*} test varName-5 {} -setup {set _a 3*in} -body { uexpr {3*$_a=in} } -cleanup {unset _a} -result {9.0 in} test varName-6 {verify feature was removed} -setup {set a(b) 3*in} -body { uexpr {3*$a.b=in} } -cleanup {unset a} -returnCodes error -match glob \ -result "*a.b is not a variable name*" test varName-6.1 {} -setup {set a(3) 3*in} -body { uexpr {3*$a.b=in} } -cleanup {unset a} -returnCodes error -match glob \ -result "*a.b is not a variable name*" test unitName-1 {} -setup $SETUP -body { uexpr {3*:in=in} } -cleanup $CLEANUP -result {3.0 in} test unitName-2 {} -setup $SETUP -body { |
︙ | ︙ | |||
555 556 557 558 559 560 561 | -result "* with index*is not an array variable name*" test array-25 {} -setup $arraySetup -body { set vv [uexpr ((1,2,3),(11,22,33,44)),(10,20,30,50),666] uexpr vv..(0,1,3) } -cleanup $arrayCleanup -match real -result 44 | | | > | 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 | -result "* with index*is not an array variable name*" test array-25 {} -setup $arraySetup -body { set vv [uexpr ((1,2,3),(11,22,33,44)),(10,20,30,50),666] uexpr vv..(0,1,3) } -cleanup $arrayCleanup -match real -result 44 test array-26 {no value at given index} -setup $arraySetup -body { uexpr::uset vv ((1,2,3),(11,22,33,44)),(10,20,30,50),666 uexpr vv..(1,2) } -cleanup $arrayCleanup -returnCodes error -match glob \ -result "*array vv has no value for index 1 2*" # LaTeX for arrays test ltxArray-1 {} -setup $arraySetup -body { uexpr::ltxExpr vv..0 |
︙ | ︙ | |||
1784 1785 1786 1787 1788 1789 1790 | test +CR-3 {} -setup $SETUP -body { set a 6*psi uexpr 144*lbf/ft^2 ++ a = psi } -cleanup $CLEANUP -result {7.0 psi} test +CR-4 {} -setup $SETUP -body { lindex [uexpr::ltxExpr 4++5] 0 | | | | | | | | | | > > > > > > > > > > > > > > > > > > | > > > | | | | | | 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 | test +CR-3 {} -setup $SETUP -body { set a 6*psi uexpr 144*lbf/ft^2 ++ a = psi } -cleanup $CLEANUP -result {7.0 psi} test +CR-4 {} -setup $SETUP -body { lindex [uexpr::ltxExpr 4++5] 0 } -cleanup $CLEANUP -result { \begin{array}{c} 4 + \\ \mbox{} + 5 \end{array} } test +CR-5 {} -setup $SETUP -body { lindex [uexpr::ltxExpr (4-1)++(5-3)] 0 } -cleanup $CLEANUP -result { \begin{array}{c} \left ( 4-1 \right ) + \\ \mbox{} + \left ( 5-3 \right ) \end{array} } test +CR-6 {} -setup $SETUP -body { lindex [uexpr::ltxExpr 3*(4++5)] 0 } -cleanup $CLEANUP -result {3\cdot \left ( \begin{array}{c} 4 + \\ \mbox{} + 5 \end{array} \right ) } # test the ++- cases test +CR-7 {} -setup $SETUP -body { lindex [uexpr::ltxExpr 4++-5] 0 } -cleanup $CLEANUP -result { \begin{array}{c} 4 + \\ \mbox{} + -5 \end{array} } test +CR-8 {} -setup $SETUP -body { lindex [uexpr::ltxExpr (4-1)++-(5-3)] 0 } -cleanup $CLEANUP -result { \begin{array}{c} \left ( 4-1 \right ) + \\ \mbox{} + - \left ( 5-3 \right ) \end{array} } # test multiple ++ -- cases test +CR-9 {} -setup $SETUP -body { lindex [uexpr::ltxExpr 4++5++6] 0 } -cleanup $CLEANUP -result { \begin{array}{c} 4 + \\ \mbox{} + 5 + \\ \mbox{} + 6 \end{array} } test +CR-10 {} -setup $SETUP -body { lindex [uexpr::ltxExpr 4++5--6] 0 } -cleanup $CLEANUP -result { \begin{array}{c} 4 + \\ \mbox{} + 5 - \\ \mbox{} - 6 \end{array} } test +CR-11 {} -setup $SETUP -body { uexpr 4 ++ 5 ++ 6 -- 7+5 } -cleanup $CLEANUP -result 13 test +CR-12 {} -setup $SETUP -body { uexpr 3 - 4 ++ 5 - 6 ++ 7 - 8 -- 9 + 5 } -cleanup $CLEANUP -result -7 test +CR-13 {} -setup $SETUP -body { uexpr 2*3 - 4 ++ 5 - 6 ++ 7 - 8 -- 9 + 5*3 } -cleanup $CLEANUP -result 6 test +CR-14 {} -setup $SETUP -body { uexpr 2*3 - 4 ++ 5 - 6 ++ 7 - 8 -- 9*2 + 5*3 } -cleanup $CLEANUP -result -3 test +CR-15 {} -setup $SETUP -body { uexpr 2*3 - 4 ++ 5 - 6 ++ 7 - 8 -- 3^2 + 5*3 } -cleanup $CLEANUP -result 6.0 # line breaking subtract test -CR {} -setup $SETUP -body { uexpr 4 -- 5 } -cleanup $CLEANUP -result -1 test -CR-1 {} -setup $SETUP -body { set a 6 uexpr 2 - 3 + 4 -- a + 5 + 7 } -cleanup $CLEANUP -result 9 test -CR-1.1 {} -setup $SETUP -body { set a 6 uexpr 2 + 3 - 4 -- a - 5 + 7 } -cleanup $CLEANUP -result -3 # the error message is not ideal, but probably good enough test -CR-2 {} -setup $SETUP -body { set a 6*psi uexpr 4 -- a } -cleanup $CLEANUP -returnCodes error -match glob \ -result {*trying to add values with different units*} test -CR-3 {} -setup $SETUP -body { set a 6*psi uexpr 144*lbf/ft^2 -- a = psi } -cleanup $CLEANUP -result {-5.0 psi} test -CR-4 {} -setup $SETUP -body { lindex [uexpr::ltxExpr 4--5] 0 } -cleanup $CLEANUP -result { \begin{array}{c} 4 - \\ \mbox{} - 5 \end{array} } test -CR-5 {} -setup $SETUP -body { lindex [uexpr::ltxExpr (4-1)--(5-3)] 0 } -cleanup $CLEANUP -result { \begin{array}{c} \left ( 4-1 \right ) - \\ \mbox{} - \left ( 5-3 \right ) \end{array} } test -CR-6 {} -setup $SETUP -body { lindex [uexpr::ltxExpr 3*(4--5)] 0 } -cleanup $CLEANUP -result {3\cdot \left ( \begin{array}{c} 4 - \\ \mbox{} - 5 \end{array} \right ) } test subtract {} -setup $SETUP -body { uexpr 4 - 5 } -cleanup $CLEANUP -result -1 |
︙ | ︙ | |||
2449 2450 2451 2452 2453 2454 2455 2456 2457 2458 2459 2460 2461 2462 2463 2464 2465 2466 2467 2468 | test unitsLike-1 {} -setup $SETUP -body { expr {"psi" in [uexpr::unitsLike bar]} } -result 1 test unitNames-1 {} -setup $SETUP -body { uexpr::unitNames bar } -result bar cleanupTests } #namespace delete ::uexpr::test | > > > > > | 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 | test unitsLike-1 {} -setup $SETUP -body { expr {"psi" in [uexpr::unitsLike bar]} } -result 1 test unitsLike-2 {} -setup $SETUP -body { expr {"watt" in [uexpr::unitsLike BTU / hr]} } -result 1 test unitNames-1 {} -setup $SETUP -body { uexpr::unitNames bar } -result bar cleanupTests } #namespace delete ::uexpr::test |