uexpr

Check-in [f7694b2246]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:works,, list/vector/tcl array access will change
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | trunk
Files: files | file ages | folders
SHA3-256: f7694b22465ebaf282409602a0b3705ed6986795fe43b9109704d76418bc5ed5
User & Date: davebr 2024-04-06 13:42:15
Context
2024-04-06
13:42
works,, list/vector/tcl array access will change Leaf check-in: f7694b2246 user: davebr tags: trunk
2024-02-02
16:34
correct typos in man page, add uexpr.n for systems without dtplite check-in: cbc58d1e41 user: davebr tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to uexpr.dt.

25
26
27
28
29
30
31
32

33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
can be written:
[example "= 4*ft+5*in=m"]

Expressions consist of operands and operators sometimes separated by spaces.

[section Operands]

Operands can be numbers or numbers with units. [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 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.








|
>
|
|







|







25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
can be written:
[example "= 4*ft+5*in=m"]

Expressions consist of operands and operators sometimes separated by spaces.

[section Operands]

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.

63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79

[example ft/sec]

defines a unit of speed.

[example "BTU/hr ft^2"]

defines a heat flux unit (energy per unit area and time). Note the space
between units in the denominator is an implied multiply, which has
higher precedence than the divide. (see IMPLIED MULTIPLY).

Unit specifiers are used in operands, and to specify the units of the results
from [cmd uexpr].

[subsection "Values with Units"]

Values with units are simple expressions containing a pure number







|

|







64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80

[example ft/sec]

defines a unit of speed.

[example "BTU/hr ft^2"]

defines a heat flux (energy per unit area and time). Note the space
between units in the denominator is an implied multiply, which has
higher precedence than the divide. See implied multiply in [sectref Operators].

Unit specifiers are used in operands, and to specify the units of the results
from [cmd uexpr].

[subsection "Values with Units"]

Values with units are simple expressions containing a pure number
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
[cmd uexpr].

[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 ":", see 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 it is looked up in a
function's parameter list (if it is used in a function definition),
then in the context of the uexpr call. If the name contains at least
one "." it is separated at the first "." into an array name and array
index. If none of those exist the name is looked up in the units
dictionary.


If a name does not follow a "$" or ":", then:


[list_begin itemized]

[item] if it is 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] If the name contains at least one "." it is separated at the
first "." into an array name and array index.  [example h.suct]
becomes

[example h(suct)]

This was done to give access to Tcl arrays and is kind of ugly. It is likely to change if
a less ambiguous way to reference Tcl arrays can be devised.  Perhaps
just allow name(member) like Tcl, using a $ prefix to resolve function
name conflicts.

[item] If none of those exist the name is looked up in the units
dictionary.

[item] A error occurs if none of these exist.

[list_end]

[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.







|





<

<
<
<
<
<
|


|
>


>
|


|







|
|
|
|

|


|







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
[cmd uexpr].

[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 ":", 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] If the name contains at least one "." it is separated at the
first "." into an array name and array index.  [example h.suct]
becomes

[example h(suct)]

This was done to give access to Tcl arrays and is kind of ugly. It is
likely to change if a less ambiguous way to reference Tcl arrays can
be devised.  Perhaps just allow name(member) like Tcl, using a $
prefix to resolve conflicts with function names.

[item] The name is looked up in the units
dictionary.

[item] An error occurs if no value was found..

[list_end]

[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.
204
205
206
207
208
209
210
211
212
213


214
215
216
217
218
219
220
221
222
223
224
225
[example "=m..(2,0)"]
returns a value of 111.

[para]
This is rather fragile. If the array indexes do not match the actual array structure, there may not be any error, and odd results will be returned.


[item] "** ^ exponentiation. The right argument (exponent) must be
unit less. The left argument can have units. However if the
exponent is not an integer, the results may not be useful if it does.  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. Useful in unit specifiers.

[item] / divide

[item] * multiply

[item] Unary + - operators to the left of their operand
have a minimum precedence between addition or subtraction and







|
|
|
>
>




|







201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
[example "=m..(2,0)"]
returns a value of 111.

[para]
This is rather fragile. If the array indexes do not match the actual array 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. Useful in unit specifiers.

[item] / divide

[item] * multiply

[item] Unary + - operators to the left of their operand
have a minimum precedence between addition or subtraction and
236
237
238
239
240
241
242







243
244
245
246
247
248
249
[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.








[item] "," separate items in a list

[item] "=" is a very low precedence divide right now, however
the normal processing of expressions keeps "=" from being
interpreted as part of an expression.








>
>
>
>
>
>
>







235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
[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

[item] "=" is a very low precedence divide right now, however
the normal processing of expressions keeps "=" from being
interpreted as part of an expression.

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
[item] Trigonometric Functions (sin cos tan) expect a single angle argument (deg rad
...).and return a pure number.

[item] Inverse Trigonometric Functions (asin acos atan) expect a pure
number and return an angle result. atan2 expects two arguments with
compatible units.

[item] Hyperbolic functions (sinh cosh tanh) expect unit less arguments and return a unit less result.

[item] ceil int floor round round up, toward zero, down and down to the
nearest integer respectively. They accept only unit less arguments, and
return an integer valued double precision real number.

[item] hypot fmod max min accept any values as long as they have compatible units.

[item] abs accepts one argument with any units

[item] sqrt accepts any positive argument with any units. Returns the
appropriate units. If the argument basis unit exponents are not multiples of 2,
the results may be useless.

[item] exp log log10 accept one argument with no units.

[item] rand returns a pure number.

[item] srand accepts any units, but only uses the argument magnitude (ignoring
basis unit exponents). It returns a unit less number.

[list_end]

[para]

Some functions available in [cmd expr] are not available in
[package uexpr]. Type conversion functions (wide entier double bool) integer
functions (isqrt) number format functions (isordered issubnormal isnan
is normal isinf isfinite) are not implemented.

[para]

There are additional functions not normally found in [cmd expr]: 

[list_begin itemized]




















[item] [fun hasUnits(expression)] returns 0 if the value is a pure number.

[example hasUnits(3)]

returns 0








|


|















|















>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







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
[item] Trigonometric Functions (sin cos tan) expect a single angle argument (deg rad
...).and return a pure number.

[item] Inverse Trigonometric Functions (asin acos atan) expect a pure
number and return an angle result. atan2 expects two arguments with
compatible units.

[item] Hyperbolic functions (sinh cosh tanh) expect unitless arguments and return a unitless result.

[item] ceil int floor round round up, toward zero, down and down to the
nearest integer respectively. They accept only unitless arguments, and
return an integer valued double precision real number.

[item] hypot fmod max min accept any values as long as they have compatible units.

[item] abs accepts one argument with any units

[item] sqrt accepts any positive argument with any units. Returns the
appropriate units. If the argument basis unit exponents are not multiples of 2,
the results may be useless.

[item] exp log log10 accept one argument with no units.

[item] rand returns a pure number.

[item] srand accepts any units, but only uses the argument magnitude (ignoring
basis unit exponents). It returns a unitless number.

[list_end]

[para]

Some functions available in [cmd expr] are not available in
[package uexpr]. Type conversion functions (wide entier double bool) integer
functions (isqrt) number format functions (isordered issubnormal isnan
is normal isinf isfinite) are not implemented.

[para]

There are additional functions not normally found in [cmd expr]: 

[list_begin itemized]

[item] [fun sum(var,first_index,last_index,expression)] 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

[example "sum(i,0,4,2^i)"]

will return 31.0

[para]
A simple polynomial with coefficients in c

[example {set c {1 2 3 4 5}}]
[example {set x 0.2}]
[example "= sum(i,0,4,c..i*x^i)"]

will return 1.56

[item] [fun hasUnits(expression)] returns 0 if the value is a pure number.

[example hasUnits(3)]

returns 0

360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415

returns something close to "151.6000...002 mm"

[example "uexpr 15 psi = in wc = bar"]

returns something like "1.0342...41 bar", note only the last result unit specifier was used

[example "uexpr 15*psi= in wc"]

returns something like "415.609...18 in wc".
Note: "in wc" is inches water column.

[call [cmd ltxExpr] [arg expression] [opt [arg expression]] ...]

[cmd ltxExpr] concatenates its operands separated by a space and
evaluates the expression. It returns a list of results. The list
always contains the expression formatted for LaTeX, and the result as
a unit expression (which can be used in an expression for uexpr or
ltxExpr). In addition if the expression ends with one or more result unit
specifiers, a magnitude and unit specifier (formatted for LaTeX) is
returned for each result unit specifier. If any of 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 no units are specified, a magnitude and
unit specifier (formatted for LaTeX) containing only base units will
be generated. ltxExpr is intended for use in the [package calc] package.

[example "ltxExpr 3*ft=in=cm"]

returns "{3\cdot \mathrm{ft}} {36.0 in} 36.0 {\mathrm{in}} 91.44 {\mathrm{cm}}"

[para]

[cmd ltxExpr] formats variable names to include symbols and
subscripts. The "_" prefix followed by a character will insert a LaTeX
symbol, typically a Greek letter. "." indicates the following
characters are part of a subscript. Multiple subscripts are
allowed. For example the variable name:

[example _a.4]
generates the following LaTeX: {\alpha _{\mathrm{4}}}

[call [cmd uexpr::unitNames] [opt [arg "glob pattern"]]]
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 unit less constants are returned.

[call [cmd uexpr::newUnit] [arg name] [arg expression]]

[cmd uexpr::newUnit] adds another unit to the unit array. The
expression defines the new unit in terms of any existing base or defined
unit. The unit array is common to all contexts within a Tcl
interpreter. These changes are global. For example a mile is defined







|




|













|





|















|







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

returns something close to "151.6000...002 mm"

[example "uexpr 15 psi = in wc = bar"]

returns something like "1.0342...41 bar", note only the last result unit specifier was used

[example "uexpr 15*psi = in wc"]

returns something like "415.609...18 in wc".
Note: "in wc" is inches water column.

[call [cmd ::uexpr::ltxExpr] [arg expression] [opt [arg expression]] ...]

[cmd ltxExpr] concatenates its operands separated by a space and
evaluates the expression. It returns a list of results. The list
always contains the expression formatted for LaTeX, and the result as
a unit expression (which can be used in an expression for uexpr or
ltxExpr). In addition if the expression ends with one or more result unit
specifiers, a magnitude and unit specifier (formatted for LaTeX) is
returned for each result unit specifier. If any of 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 no units are specified, a magnitude and
unit specifier (formatted for LaTeX) containing only base units will
be generated. ltxExpr is intended for use in the [package calc] package.

[example "uexpr::ltxExpr 3*ft=in=cm"]

returns "{3\cdot \mathrm{ft}} {36.0 in} 36.0 {\mathrm{in}} 91.44 {\mathrm{cm}}"

[para]

[cmd uexpr::ltxExpr] formats variable names to include symbols and
subscripts. The "_" prefix followed by a character will insert a LaTeX
symbol, typically a Greek letter. "." indicates the following
characters are part of a subscript. Multiple subscripts are
allowed. For example the variable name:

[example _a.4]
generates the following LaTeX: {\alpha _{\mathrm{4}}}

[call [cmd uexpr::unitNames] [opt [arg "glob pattern"]]]
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]]

[cmd uexpr::newUnit] adds another unit to the unit array. The
expression defines the new unit in terms of any existing base or defined
unit. The unit array is common to all contexts within a Tcl
interpreter. These changes are global. For example a mile is defined
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
follows. For example to add two lengths in inches, and return the
result in centimeters:

[example "[cmd uexpr] 4*in+5*in=cm"]

Or using ltxExpr, to get the results in cm and feet:

[example "[cmd ltxExpr] 4*in+3*in=cm=ft"]

[item] There are no type conversion functions (double entier,
wide). int() exists, and returns a value that looks like an integer,
but it is treated like a real number.

[item] all functions available to uexpr and friends are in
::uexpr::ufunc.

[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 (/).

[example "3 ft/3 in == (3*ft)/(3*in) == 12.0"]

However

[example "3*ft/3*in == 0.083333...*ft^2"]

Probably not what was desired.




[item] Numbers can be raised to a power using "**" or "^". Currently
"**" and "^" work as shown in Abramowitz and Stegun "Handbook of Mathematical Functions ..." rather than Tcl's [cmd expr].

[example "uexpr -2^4^2"]
returns -65536.







|


|
<

















>
>







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
follows. For example to add two lengths in inches, and return the
result in centimeters:

[example "[cmd uexpr] 4*in+5*in=cm"]

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
::uexpr::ufunc.

[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 (/).

[example "3 ft/3 in == (3*ft)/(3*in) == 12.0"]

However

[example "3*ft/3*in == 0.083333...*ft^2"]

Probably not what was desired.

[item] Comparisons can be chained. 1<a<3 is true if the value in a is between 1 and 3.


[item] Numbers can be raised to a power using "**" or "^". Currently
"**" and "^" work as shown in Abramowitz and Stegun "Handbook of Mathematical Functions ..." rather than Tcl's [cmd expr].

[example "uexpr -2^4^2"]
returns -65536.
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567


568
569
570
571
572
573
574


[section Examples]


[subsection "Mass and Force Units"]

In US Customary units pounds (lb) can refer to a mass or the force
it exerts in a 1 g gravity field. This causes all sorts of mysterious
gc factors (typically about 32.174) to appear in formulas. In the
[package uexpr] package, all US Customary mass units have an "m"
suffix (lbm ozm ...) and all US Customary force units have an "f" suffix (lbf ozf
...) the base unit name (lb, oz ...) is not defined on purpose, so
its use will cause an undefined value error. This avoids the inevitable
units mismatch which occurs further on in the calculation.



[para] Metric units (SI) have managed to avoid this problem (mass is
Kg or gram, force is newton N), although I've started to see Kg (and
sometimes Kgf) show up as a force unit in some places. It was good
while it lasted.

[subsection "Temperature and Pressure Units"]







|
|
|

|
|
|
|
>
>







579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602


[section Examples]


[subsection "Mass and Force Units"]

In US Customary units pounds (lb) can refer to a mass or the force it
exerts in a 1 g gravity field. This causes all sorts of mysterious gc
factors (typically about 32.174) to appear in formulas. In the
[package uexpr] package, all US Customary mass units have an "m"
suffix (lbm ozm ...) and all US Customary force units have an "f"
suffix (lbf ...) the base unit name (lb, ...) is not defined on
purpose, so its use will cause an undefined value error. This avoids
the inevitable units mismatch which occurs further on in the
calculation. oz is actually defined as a volume unit. while ozm is a
mass unit.

[para] Metric units (SI) have managed to avoid this problem (mass is
Kg or gram, force is newton N), although I've started to see Kg (and
sometimes Kgf) show up as a force unit in some places. It was good
while it lasted.

[subsection "Temperature and Pressure Units"]
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
the exact result 27.0*ft.

[para]

Formulas using customary units may end up with fractional
exponents. The calculation of liquid flow rate through a valve above
is an example.  The procedure to format the formula for self
consistent units, results in all calculations being done with unit
less values, so there is no problem with fractional basis unit
exponents.

[para]

Some formulas in fluid mechanics and heat transfer raise unit less
numbers such as Reynolds number, Prandtl number and Nusselt number to
odd decimal fractional powers. However as these are unit less
parameters, there is no problem with fractional exponents on basis
units.

[para]

Another example is modeling a compressor as an isentropic or
polytropic process, where volume ratios are raised to powers between
1.4 and 1.1 (isentropic volume exponent). Once again the volume ratio
is unit less, and there are no fractional exponents on the basis units.




[keywords Calculations uexpr]
[manpage_end]







|
|
<



|

|








|






733
734
735
736
737
738
739
740
741

742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
the exact result 27.0*ft.

[para]

Formulas using customary units may end up with fractional
exponents. The calculation of liquid flow rate through a valve above
is an example.  The procedure to format the formula for self
consistent units, results in all calculations being done with unitless
values, so there is no problem with fractional basis unit exponents.


[para]

Some formulas in fluid mechanics and heat transfer raise unitless
numbers such as Reynolds number, Prandtl number and Nusselt number to
odd decimal fractional powers. However as these are unitless
parameters, there is no problem with fractional exponents on basis
units.

[para]

Another example is modeling a compressor as an isentropic or
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.




[keywords Calculations uexpr]
[manpage_end]

Changes to uexpr.html.

111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
<ul>
<li class="doctools_subsection"><a href="#subsection1">Numbers</a></li>
<li class="doctools_subsection"><a href="#subsection2">Unit Specifier</a></li>
<li class="doctools_subsection"><a href="#subsection3">Values with Units</a></li>
<li class="doctools_subsection"><a href="#subsection4">Named Values</a></li>
<li class="doctools_subsection"><a href="#subsection5">Variables</a></li>
<li class="doctools_subsection"><a href="#subsection6">Units and Constants</a></li>
<li class="doctools_subsection"><a href="#subsection7">NAME RESOLUTION</a></li>
<li class="doctools_subsection"><a href="#subsection8">Named Values and Implied Multiply</a></li>
</ul>
</li>
<li class="doctools_section"><a href="#section3">Operators</a></li>
<li class="doctools_section"><a href="#section4">Math Functions</a></li>
<li class="doctools_section"><a href="#section5">Commands</a></li>
<li class="doctools_section"><a href="#section6">Differences between <b class="cmd">uexpr</b> and <b class="cmd">expr</b></a></li>







|







111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
<ul>
<li class="doctools_subsection"><a href="#subsection1">Numbers</a></li>
<li class="doctools_subsection"><a href="#subsection2">Unit Specifier</a></li>
<li class="doctools_subsection"><a href="#subsection3">Values with Units</a></li>
<li class="doctools_subsection"><a href="#subsection4">Named Values</a></li>
<li class="doctools_subsection"><a href="#subsection5">Variables</a></li>
<li class="doctools_subsection"><a href="#subsection6">Units and Constants</a></li>
<li class="doctools_subsection"><a href="#subsection7">Name Resolution</a></li>
<li class="doctools_subsection"><a href="#subsection8">Named Values and Implied Multiply</a></li>
</ul>
</li>
<li class="doctools_section"><a href="#section3">Operators</a></li>
<li class="doctools_section"><a href="#section4">Math Functions</a></li>
<li class="doctools_section"><a href="#section5">Commands</a></li>
<li class="doctools_section"><a href="#section6">Differences between <b class="cmd">uexpr</b> and <b class="cmd">expr</b></a></li>
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
<ul class="doctools_requirements">
<li>package require <b class="pkgname">Tcl 8.6</b></li>
<li>package require <b class="pkgname">uexpr <span class="opt">?0.1?</span></b></li>
<li>package require <b class="pkgname">uexpr= <span class="opt">?0.1?</span></b></li>
</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">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">Parameters</i> <i class="arg">expression</i></a></li>
</ul>
</div>
</div>







|







140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
<ul class="doctools_requirements">
<li>package require <b class="pkgname">Tcl 8.6</b></li>
<li>package require <b class="pkgname">uexpr <span class="opt">?0.1?</span></b></li>
<li>package require <b class="pkgname">uexpr= <span class="opt">?0.1?</span></b></li>
</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">Parameters</i> <i class="arg">expression</i></a></li>
</ul>
</div>
</div>
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
<p>Package <b class="package">uexpr=</b> loads the <b class="package">uexpr</b> package and aliases <b class="cmd">=</b> to <b class="cmd">uexpr</b>. For example:</p>
<pre class="doctools_example">uexpr 4*ft+5*in=m</pre>
<p>can be written:</p>
<pre class="doctools_example">= 4*ft+5*in=m</pre>
<p>Expressions consist of operands and operators sometimes separated by spaces.</p>
</div>
<div id="section2" class="doctools_section"><h2><a name="section2">Operands</a></h2>
<p>Operands can be numbers or numbers with units. <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 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 &quot;e&quot; 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 &quot;e&quot; and &quot;E&quot;
are interpreted as variables or units.
Keep this in mind if scientific notation is used in an expression and
units or variables named &quot;e or &quot;E&quot; 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>
<pre class="doctools_example">ft/sec</pre>
<p>defines a unit of speed.</p>
<pre class="doctools_example">BTU/hr ft^2</pre>
<p>defines a heat flux unit (energy per unit area and time). Note the space
between units in the denominator is an implied multiply, which has
higher precedence than the divide. (see IMPLIED MULTIPLY).
Unit specifiers are used in operands, and to specify the units of the results
from <b class="cmd">uexpr</b>.</p>
</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







|
>
|
|





|



















|

|







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
<p>Package <b class="package">uexpr=</b> loads the <b class="package">uexpr</b> package and aliases <b class="cmd">=</b> to <b class="cmd">uexpr</b>. For example:</p>
<pre class="doctools_example">uexpr 4*ft+5*in=m</pre>
<p>can be written:</p>
<pre class="doctools_example">= 4*ft+5*in=m</pre>
<p>Expressions consist of operands and operators sometimes separated by spaces.</p>
</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 &quot;e&quot; 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 &quot;e&quot; and &quot;E&quot;
are interpreted as variables or units.
Keep this in mind if scientific notation is used in an expression and
units or variables named &quot;e or &quot;E&quot; 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>
<pre class="doctools_example">ft/sec</pre>
<p>defines a unit of speed.</p>
<pre class="doctools_example">BTU/hr ft^2</pre>
<p>defines a heat flux (energy per unit area and time). Note the space
between units in the denominator is an implied multiply, which has
higher precedence than the divide. See implied multiply in <span class="sectref"><a href="#section3">Operators</a></span>.
Unit specifiers are used in operands, and to specify the units of the results
from <b class="cmd">uexpr</b>.</p>
</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
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
braced {}, to prevent Tcl from bypassing the variable handling by
<b class="cmd">uexpr</b>.</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 &quot;:&quot; it will
only be looked up in the units dictionary.  If a unit or
constant name does not follow &quot;:&quot;, see NAME RESOLUTION 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 &quot;$&quot; or &quot;:&quot;, then it is looked up in a
function's parameter list (if it is used in a function definition),
then in the context of the uexpr call. If the name contains at least
one &quot;.&quot; it is separated at the first &quot;.&quot; into an array name and array
index. If none of those exist the name is looked up in the units
dictionary.
If a name does not follow a &quot;$&quot; or &quot;:&quot;, then:</p>
<ul class="doctools_itemized">
<li><p>if it is 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>If the name contains at least one &quot;.&quot; it is separated at the
first &quot;.&quot; into an array name and array index.</p>
<pre class="doctools_example">h.suct</pre>
<p>becomes</p>
<pre class="doctools_example">h(suct)</pre>
<p>This was done to give access to Tcl arrays and is kind of ugly. It is likely to change if
a less ambiguous way to reference Tcl arrays can be devised.  Perhaps
just allow name(member) like Tcl, using a $ prefix to resolve function
name conflicts.</p></li>
<li><p>If none of those exist the name is looked up in the units
dictionary.</p></li>
<li><p>A error occurs if none of these exist.</p></li>
</ul>
</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







|



|
|
<
|
<
<
<
<

|

|





|
|
|
|
|

|







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
braced {}, to prevent Tcl from bypassing the variable handling by
<b class="cmd">uexpr</b>.</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 &quot;:&quot; it will
only be looked up in the units dictionary.  If a unit or
constant name does not follow &quot;:&quot;, 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 &quot;$&quot; or &quot;:&quot; 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>If the name contains at least one &quot;.&quot; it is separated at the
first &quot;.&quot; into an array name and array index.</p>
<pre class="doctools_example">h.suct</pre>
<p>becomes</p>
<pre class="doctools_example">h(suct)</pre>
<p>This was done to give access to Tcl arrays and is kind of ugly. It is
likely to change if a less ambiguous way to reference Tcl arrays can
be devised.  Perhaps just allow name(member) like Tcl, using a $
prefix to resolve conflicts with function names.</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>
</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
297
298
299
300
301
302
303
304
305
306


307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327



328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364












365
366
367
368
369
370
371
<pre class="doctools_example">= v..2</pre>
<p>returns the 3rd list entry (3 in this case)</p>
<pre class="doctools_example">set m [= (1,2,3),(11,22,33),(111,222,333)]</pre>
<p>saves a 3x3 array 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 array indexes do not match the actual array structure, there may not be any error, and odd results will be returned.</p></li>
<li><p>&quot;** ^ exponentiation. The right argument (exponent) must be
unit less. The left argument can have units. However if the
exponent is not an integer, the results may not be useful if it does.  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&quot; 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. Useful in unit specifiers.</p></li>
<li><p>/ divide</p></li>
<li><p>* multiply</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 <b class="cmd">expr</b>, however it appears to match the conventions in
&quot;Handbook of Mathematical Functions&quot; 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>&lt; &lt;= =&lt; == != &lt;&gt; .= =&gt; &gt; comparisons, return 1.0 for true,
0.0 for false. note ==, != and &lt;&gt; are not that useful with real
numbers. Left and right arguments must have compatible units.</p></li>



<li><p>&quot;,&quot; separate items in a list</p></li>
<li><p>&quot;=&quot; is a very low precedence divide right now, however
the normal processing of expressions keeps &quot;=&quot; from being
interpreted as part of an expression.</p></li>
</ul>
<p>&quot;( )&quot; group sub expressions (as for <b class="cmd">expr</b>).
&quot;%&quot; is a constant defined as 0.01 in the units dict.</p>
<pre class="doctools_example">45%*a = 045*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
number and return an angle result. atan2 expects two arguments with
compatible units.</p></li>
<li><p>Hyperbolic functions (sinh cosh tanh) expect unit less arguments and return a unit less result.</p></li>
<li><p>ceil int floor round round up, toward zero, down and down to the
nearest integer respectively. They accept only unit less arguments, and
return an integer valued double precision real number.</p></li>
<li><p>hypot fmod max min accept any values as long as they have compatible units.</p></li>
<li><p>abs accepts one argument with any units</p></li>
<li><p>sqrt accepts any positive argument with any units. Returns the
appropriate units. If the argument basis unit exponents are not multiples of 2,
the results may be useless.</p></li>
<li><p>exp log log10 accept one argument with no units.</p></li>
<li><p>rand returns a pure number.</p></li>
<li><p>srand accepts any units, but only uses the argument magnitude (ignoring
basis unit exponents). It returns a unit less number.</p></li>
</ul>
<p>Some functions available in <b class="cmd">expr</b> are not available in
<b class="package">uexpr</b>. Type conversion functions (wide entier double bool) integer
functions (isqrt) number format functions (isordered issubnormal isnan
is normal isinf isfinite) are not implemented.</p>
<p>There are additional functions not normally found in <b class="cmd">expr</b>:</p>
<ul class="doctools_itemized">












<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>







|
|
|
>
>



|
















|
>
>
>

















|

|









|







>
>
>
>
>
>
>
>
>
>
>
>







293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
<pre class="doctools_example">= v..2</pre>
<p>returns the 3rd list entry (3 in this case)</p>
<pre class="doctools_example">set m [= (1,2,3),(11,22,33),(111,222,333)]</pre>
<p>saves a 3x3 array 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 array indexes do not match the actual array 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>&quot;implied multiply&quot; 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. Useful in unit specifiers.</p></li>
<li><p>/ divide</p></li>
<li><p>* multiply</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 <b class="cmd">expr</b>, however it appears to match the conventions in
&quot;Handbook of Mathematical Functions&quot; 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>&lt; &lt;= =&lt; == != &lt;&gt; .= =&gt; &gt; comparisons, return 1.0 for true,
0.0 for false. note ==, != and &lt;&gt; 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&lt;a&lt;13</pre>
<p>will return true (1.0) if the value of a is between 7 and 13</p></li>
<li><p>&quot;,&quot; separate items in a list</p></li>
<li><p>&quot;=&quot; is a very low precedence divide right now, however
the normal processing of expressions keeps &quot;=&quot; from being
interpreted as part of an expression.</p></li>
</ul>
<p>&quot;( )&quot; group sub expressions (as for <b class="cmd">expr</b>).
&quot;%&quot; is a constant defined as 0.01 in the units dict.</p>
<pre class="doctools_example">45%*a = 045*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
number and return an angle result. atan2 expects two arguments with
compatible units.</p></li>
<li><p>Hyperbolic functions (sinh cosh tanh) expect unitless arguments and return a unitless result.</p></li>
<li><p>ceil int floor round round up, toward zero, down and down to the
nearest integer respectively. They accept only unitless arguments, and
return an integer valued double precision real number.</p></li>
<li><p>hypot fmod max min accept any values as long as they have compatible units.</p></li>
<li><p>abs accepts one argument with any units</p></li>
<li><p>sqrt accepts any positive argument with any units. Returns the
appropriate units. If the argument basis unit exponents are not multiples of 2,
the results may be useless.</p></li>
<li><p>exp log log10 accept one argument with no units.</p></li>
<li><p>rand returns a pure number.</p></li>
<li><p>srand accepts any units, but only uses the argument magnitude (ignoring
basis unit exponents). It returns a unitless number.</p></li>
</ul>
<p>Some functions available in <b class="cmd">expr</b> are not available in
<b class="package">uexpr</b>. Type conversion functions (wide entier double bool) integer
functions (isqrt) number format functions (isordered issubnormal isnan
is normal isinf isfinite) are not implemented.</p>
<p>There are additional functions not normally found in <b class="cmd">expr</b>:</p>
<ul class="doctools_itemized">
<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 simple polynomial with coefficients in c</p>
<pre class="doctools_example">set c {1 2 3 4 5}</pre>
<pre class="doctools_example">set x 0.2</pre>
<pre class="doctools_example">= sum(i,0,4,c..i*x^i)</pre>
<p>will return 1.56</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>
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
<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 &quot;151.6000...002 mm&quot;</p>
<pre class="doctools_example">uexpr 15 psi = in wc = bar</pre>
<p>returns something like &quot;1.0342...41 bar&quot;, note only the last result unit specifier was used</p>
<pre class="doctools_example">uexpr 15*psi= in wc</pre>
<p>returns something like &quot;415.609...18 in wc&quot;.
Note: &quot;in wc&quot; is inches water column.</p></dd>
<dt><a name="2"><b class="cmd">ltxExpr</b> <i class="arg">expression</i> <span class="opt">?<i class="arg">expression</i>?</span> ...</a></dt>
<dd><p><b class="cmd">ltxExpr</b> concatenates its operands separated by a space and
evaluates the expression. It returns a list of results. The list
always contains the expression formatted for LaTeX, and the result as
a unit expression (which can be used in an expression for uexpr or
ltxExpr). In addition if the expression ends with one or more result unit
specifiers, a magnitude and unit specifier (formatted for LaTeX) is
returned for each result unit specifier. If any of 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 no units are specified, a magnitude and
unit specifier (formatted for LaTeX) containing only base units will
be generated. ltxExpr is intended for use in the <b class="package">calc</b> package.</p>
<pre class="doctools_example">ltxExpr 3*ft=in=cm</pre>
<p>returns &quot;{3\cdot \mathrm{ft}} {36.0 in} 36.0 {\mathrm{in}} 91.44 {\mathrm{cm}}&quot;</p>
<p><b class="cmd">ltxExpr</b> formats variable names to include symbols and
subscripts. The &quot;_&quot; prefix followed by a character will insert a LaTeX
symbol, typically a Greek letter. &quot;.&quot; indicates the following
characters are part of a subscript. Multiple subscripts are
allowed. For example the variable name:</p>
<pre class="doctools_example">_a.4</pre>
<p>generates the following LaTeX: {\alpha _{\mathrm{4}}}</p></dd>
<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 unit less constants are returned.</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 array. The
expression defines the new unit in terms of any existing base or defined
unit. The unit array 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>







|


|











|

|













|







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
<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 &quot;151.6000...002 mm&quot;</p>
<pre class="doctools_example">uexpr 15 psi = in wc = bar</pre>
<p>returns something like &quot;1.0342...41 bar&quot;, note only the last result unit specifier was used</p>
<pre class="doctools_example">uexpr 15*psi = in wc</pre>
<p>returns something like &quot;415.609...18 in wc&quot;.
Note: &quot;in wc&quot; is inches water column.</p></dd>
<dt><a name="2"><b class="cmd">::uexpr::ltxExpr</b> <i class="arg">expression</i> <span class="opt">?<i class="arg">expression</i>?</span> ...</a></dt>
<dd><p><b class="cmd">ltxExpr</b> concatenates its operands separated by a space and
evaluates the expression. It returns a list of results. The list
always contains the expression formatted for LaTeX, and the result as
a unit expression (which can be used in an expression for uexpr or
ltxExpr). In addition if the expression ends with one or more result unit
specifiers, a magnitude and unit specifier (formatted for LaTeX) is
returned for each result unit specifier. If any of 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 no units are specified, a magnitude and
unit specifier (formatted for LaTeX) containing only base units will
be generated. ltxExpr is intended for use in the <b class="package">calc</b> package.</p>
<pre class="doctools_example">uexpr::ltxExpr 3*ft=in=cm</pre>
<p>returns &quot;{3\cdot \mathrm{ft}} {36.0 in} 36.0 {\mathrm{in}} 91.44 {\mathrm{cm}}&quot;</p>
<p><b class="cmd">uexpr::ltxExpr</b> formats variable names to include symbols and
subscripts. The &quot;_&quot; prefix followed by a character will insert a LaTeX
symbol, typically a Greek letter. &quot;.&quot; indicates the following
characters are part of a subscript. Multiple subscripts are
allowed. For example the variable name:</p>
<pre class="doctools_example">_a.4</pre>
<p>generates the following LaTeX: {\alpha _{\mathrm{4}}}</p></dd>
<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></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 array. The
expression defines the new unit in terms of any existing base or defined
unit. The unit array 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>
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
<li><p>There are no bit or logical
operations (&lt;&lt; &gt;&gt; &amp; | &amp;&amp; ||).</p></li>
<li><p>a new operator &quot;=&quot; 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">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 value that looks like an integer,
but it is treated like a real number.</p></li>
<li><p>all functions available to uexpr and friends are in
::uexpr::ufunc.</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, &quot;3a&quot; will be interpreted as &quot;3 a&quot; or &quot;5ft&quot; as &quot;5 ft&quot;.
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>
<pre class="doctools_example">3*ft/3*in == 0.083333...*ft^2</pre>
<p>Probably not what was desired.</p></li>

<li><p>Numbers can be raised to a power using &quot;**&quot; or &quot;^&quot;. Currently
&quot;**&quot; and &quot;^&quot; work as shown in Abramowitz and Stegun &quot;Handbook of Mathematical Functions ...&quot; rather than Tcl's <b class="cmd">expr</b>.</p>
<pre class="doctools_example">uexpr -2^4^2</pre>
<p>returns -65536.
Where as</p>
<pre class="doctools_example">expr -2**4**2</pre>
<p>returns 65536
But then both <b class="cmd">uexpr</b> and <b class="cmd">expr</b> return the same result for:</p>
<pre class="doctools_example">expr 0-2**4**2</pre>
<p>returns -65536</p></li>
</ul>
</div>
<div id="section7" class="doctools_section"><h2><a name="section7">Examples</a></h2>
<div id="subsection9" class="doctools_subsection"><h3><a name="subsection9">Mass and Force Units</a></h3>
<p>In US Customary units pounds (lb) can refer to a mass or the force
it exerts in a 1 g gravity field. This causes all sorts of mysterious
gc factors (typically about 32.174) to appear in formulas. In the
<b class="package">uexpr</b> package, all US Customary mass units have an &quot;m&quot;
suffix (lbm ozm ...) and all US Customary force units have an &quot;f&quot; suffix (lbf ozf
...) the base unit name (lb, oz ...) is not defined on purpose, so
its use will cause an undefined value error. This avoids the inevitable
units mismatch which occurs further on in the calculation.</p>


<p>Metric units (SI) have managed to avoid this problem (mass is
Kg or gram, force is newton N), although I've started to see Kg (and
sometimes Kgf) show up as a force unit in some places. It was good
while it lasted.</p>
</div>
<div id="subsection10" class="doctools_subsection"><h3><a name="subsection10">Temperature and Pressure Units</a></h3>
<p><b class="cmd">uexpr</b> only does scaling unit conversions automatically. In







|


|
<











>














|
|
|

|
|
|
|
>
>







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
<li><p>There are no bit or logical
operations (&lt;&lt; &gt;&gt; &amp; | &amp;&amp; ||).</p></li>
<li><p>a new operator &quot;=&quot; 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
::uexpr::ufunc.</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, &quot;3a&quot; will be interpreted as &quot;3 a&quot; or &quot;5ft&quot; as &quot;5 ft&quot;.
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>
<pre class="doctools_example">3*ft/3*in == 0.083333...*ft^2</pre>
<p>Probably not what was desired.</p></li>
<li><p>Comparisons can be chained. 1&lt;a&lt;3 is true if the value in a is between 1 and 3.</p></li>
<li><p>Numbers can be raised to a power using &quot;**&quot; or &quot;^&quot;. Currently
&quot;**&quot; and &quot;^&quot; work as shown in Abramowitz and Stegun &quot;Handbook of Mathematical Functions ...&quot; rather than Tcl's <b class="cmd">expr</b>.</p>
<pre class="doctools_example">uexpr -2^4^2</pre>
<p>returns -65536.
Where as</p>
<pre class="doctools_example">expr -2**4**2</pre>
<p>returns 65536
But then both <b class="cmd">uexpr</b> and <b class="cmd">expr</b> return the same result for:</p>
<pre class="doctools_example">expr 0-2**4**2</pre>
<p>returns -65536</p></li>
</ul>
</div>
<div id="section7" class="doctools_section"><h2><a name="section7">Examples</a></h2>
<div id="subsection9" class="doctools_subsection"><h3><a name="subsection9">Mass and Force Units</a></h3>
<p>In US Customary units pounds (lb) can refer to a mass or the force it
exerts in a 1 g gravity field. This causes all sorts of mysterious gc
factors (typically about 32.174) to appear in formulas. In the
<b class="package">uexpr</b> package, all US Customary mass units have an &quot;m&quot;
suffix (lbm ozm ...) and all US Customary force units have an &quot;f&quot;
suffix (lbf ...) the base unit name (lb, ...) is not defined on
purpose, so its use will cause an undefined value error. This avoids
the inevitable units mismatch which occurs further on in the
calculation. oz is actually defined as a volume unit. while ozm is a
mass unit.</p>
<p>Metric units (SI) have managed to avoid this problem (mass is
Kg or gram, force is newton N), although I've started to see Kg (and
sometimes Kgf) show up as a force unit in some places. It was good
while it lasted.</p>
</div>
<div id="subsection10" class="doctools_subsection"><h3><a name="subsection10">Temperature and Pressure Units</a></h3>
<p><b class="cmd">uexpr</b> only does scaling unit conversions automatically. In
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
<p>If that result is cubed, the exponent on feet would be 0.999999,
however the residual exponent after calculating the answer in feet is
-0.000001, within the -0.01 to 0.01 range for it to be ignored, giving
the exact result 27.0*ft.</p>
<p>Formulas using customary units may end up with fractional
exponents. The calculation of liquid flow rate through a valve above
is an example.  The procedure to format the formula for self
consistent units, results in all calculations being done with unit
less values, so there is no problem with fractional basis unit
exponents.</p>
<p>Some formulas in fluid mechanics and heat transfer raise unit less
numbers such as Reynolds number, Prandtl number and Nusselt number to
odd decimal fractional powers. However as these are unit less
parameters, there is no problem with fractional exponents on basis
units.</p>
<p>Another example is modeling a compressor as an isentropic or
polytropic process, where volume ratios are raised to powers between
1.4 and 1.1 (isentropic volume exponent). Once again the volume ratio
is unit less, 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 &copy; 2021 J.D Bruchie (BSD License)</p>
</div>
</div></body></html>







|
|
<
|

|





|









657
658
659
660
661
662
663
664
665

666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
<p>If that result is cubed, the exponent on feet would be 0.999999,
however the residual exponent after calculating the answer in feet is
-0.000001, within the -0.01 to 0.01 range for it to be ignored, giving
the exact result 27.0*ft.</p>
<p>Formulas using customary units may end up with fractional
exponents. The calculation of liquid flow rate through a valve above
is an example.  The procedure to format the formula for self
consistent units, results in all calculations being done with unitless
values, so there is no problem with fractional basis unit exponents.</p>

<p>Some formulas in fluid mechanics and heat transfer raise unitless
numbers such as Reynolds number, Prandtl number and Nusselt number to
odd decimal fractional powers. However as these are unitless
parameters, there is no problem with fractional exponents on basis
units.</p>
<p>Another example is modeling a compressor as an isentropic or
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.</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 &copy; 2021 J.D Bruchie (BSD License)</p>
</div>
</div></body></html>

Changes to uexpr.n.

278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
.sp
package require \fBuexpr  ?0\&.1?\fR
.sp
package require \fBuexpr=  ?0\&.1?\fR
.sp
\fBuexpr\fR \fIexpression\fR ?\fIexpressions\fR? \&.\&.\&.
.sp
\fBltxExpr\fR \fIexpression\fR ?\fIexpression\fR? \&.\&.\&.
.sp
\fBuexpr::unitNames\fR ?\fIglob pattern\fR?
.sp
\fBuexpr::unitsLike\fR ?\fIexpression\fR?
.sp
\fBuexpr::newUnit\fR \fIname\fR \fIexpression\fR
.sp







|







278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
.sp
package require \fBuexpr  ?0\&.1?\fR
.sp
package require \fBuexpr=  ?0\&.1?\fR
.sp
\fBuexpr\fR \fIexpression\fR ?\fIexpressions\fR? \&.\&.\&.
.sp
\fB::uexpr::ltxExpr\fR \fIexpression\fR ?\fIexpression\fR? \&.\&.\&.
.sp
\fBuexpr::unitNames\fR ?\fIglob pattern\fR?
.sp
\fBuexpr::unitsLike\fR ?\fIexpression\fR?
.sp
\fBuexpr::newUnit\fR \fIname\fR \fIexpression\fR
.sp
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
can be written:
.CS

= 4*ft+5*in=m
.CE
Expressions consist of operands and operators sometimes separated by spaces\&.
.SH OPERANDS
Operands can be numbers or numbers with units\&. \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

1e-3 == 0\&.001
.CE
Numbers start after most operators or a space\&.
\fBuexpr\fR treats all numbers as double precision reals\&.
When a string can be interpreted as a number it will be\&.
.CS

3e+5e == 300000*e
.CE
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\&.







|
>
|
|











|







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
can be written:
.CS

= 4*ft+5*in=m
.CE
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

1e-3 == 0\&.001
.CE
Numbers can start after most operators or a space\&.
\fBuexpr\fR treats all numbers as double precision reals\&.
When a string can be interpreted as a number it will be\&.
.CS

3e+5e == 300000*e
.CE
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\&.
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
ft/sec
.CE
defines a unit of speed\&.
.CS

BTU/hr ft^2
.CE
defines a heat flux unit (energy per unit area and time)\&. Note the space
between units in the denominator is an implied multiply, which has
higher precedence than the divide\&. (see IMPLIED MULTIPLY)\&.
Unit specifiers are used in operands, and to specify the units of the results
from \fBuexpr\fR\&.
.SS "VALUES WITH UNITS"
Values with units are simple expressions containing a pure number
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\&.







|

|







362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
ft/sec
.CE
defines a unit of speed\&.
.CS

BTU/hr ft^2
.CE
defines a heat flux (energy per unit area and time)\&. Note the space
between units in the denominator is an implied multiply, which has
higher precedence than the divide\&. See implied multiply in \fBOperators\fR\&.
Unit specifiers are used in operands, and to specify the units of the results
from \fBuexpr\fR\&.
.SS "VALUES WITH UNITS"
Values with units are simple expressions containing a pure number
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\&.
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
RESOLUTION)\&. If a variable name follows "$" the whole expression should be
braced {}, to prevent Tcl from bypassing the variable handling by
\fBuexpr\fR\&.
.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 ":", see NAME RESOLUTION 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 it is looked up in a
function's parameter list (if it is used in a function definition),
then in the context of the uexpr call\&. If the name contains at least
one "\&." it is separated at the first "\&." into an array name and array
index\&. If none of those exist the name is looked up in the units
dictionary\&.
If a name does not follow a "$" or ":", then:

.IP \(bu
if it is 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
If the name contains at least one "\&." it is separated at the
first "\&." into an array name and array index\&.
.CS

h\&.suct
.CE
.IP
becomes
.CS

h(suct)
.CE
.IP
This was done to give access to Tcl arrays and is kind of ugly\&. It is likely to change if
a less ambiguous way to reference Tcl arrays can be devised\&.  Perhaps
just allow name(member) like Tcl, using a $ prefix to resolve function
name conflicts\&.
.IP \(bu
If none of those exist the name is looked up in the units
dictionary\&.
.IP \(bu
A error occurs if none of these exist\&.
.PP
.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







|






<
<
<
<
<
<
|
>

|


|














|
|
|
|

|


|







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
RESOLUTION)\&. If a variable name follows "$" the whole expression should be
braced {}, to prevent Tcl from bypassing the variable handling by
\fBuexpr\fR\&.
.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 ":", 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
If the name contains at least one "\&." it is separated at the
first "\&." into an array name and array index\&.
.CS

h\&.suct
.CE
.IP
becomes
.CS

h(suct)
.CE
.IP
This was done to give access to Tcl arrays and is kind of ugly\&. It is
likely to change if a less ambiguous way to reference Tcl arrays can
be devised\&.  Perhaps just allow name(member) like Tcl, using a $
prefix to resolve conflicts with function names\&.
.IP \(bu
The name is looked up in the units
dictionary\&.
.IP \(bu
An error occurs if no value was found\&.\&.
.PP
.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
508
509
510
511
512
513
514
515
516
517


518
519
520
521
522
523
524
525
526
527
528
529
530
531
=m\&.\&.(2,0)
.CE
.IP
returns a value of 111\&.
.sp
This is rather fragile\&. If the array indexes do not match the actual array structure, there may not be any error, and odd results will be returned\&.
.IP \(bu
"** ^ exponentiation\&. The right argument (exponent) must be
unit less\&. The left argument can have units\&. However if the
exponent is not an integer, the results may not be useful if it does\&.  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\&. Useful in unit specifiers\&.
.IP \(bu
/ divide
.IP \(bu
* multiply
.IP \(bu
Unary + - operators to the left of their operand
have a minimum precedence between addition or subtraction and







|
|
|
>
>






|







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
=m\&.\&.(2,0)
.CE
.IP
returns a value of 111\&.
.sp
This is rather fragile\&. If the array indexes do not match the actual array 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\&. Useful in unit specifiers\&.
.IP \(bu
/ divide
.IP \(bu
* multiply
.IP \(bu
Unary + - operators to the left of their operand
have a minimum precedence between addition or subtraction and
544
545
546
547
548
549
550








551
552
553
554
555
556
557
+ - ++ -- 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\&.








.IP \(bu
"," separate items in a list
.IP \(bu
"=" is a very low precedence divide right now, however
the normal processing of expressions keeps "=" from being
interpreted as part of an expression\&.
.PP







>
>
>
>
>
>
>
>







542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
+ - ++ -- 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
.IP \(bu
"=" is a very low precedence divide right now, however
the normal processing of expressions keeps "=" from being
interpreted as part of an expression\&.
.PP
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
Trigonometric Functions (sin cos tan) expect a single angle argument (deg rad
\&.\&.\&.)\&.and return a pure number\&.
.IP \(bu
Inverse Trigonometric Functions (asin acos atan) expect a pure
number and return an angle result\&. atan2 expects two arguments with
compatible units\&.
.IP \(bu
Hyperbolic functions (sinh cosh tanh) expect unit less arguments and return a unit less result\&.
.IP \(bu
ceil int floor round round up, toward zero, down and down to the
nearest integer respectively\&. They accept only unit less arguments, and
return an integer valued double precision real number\&.
.IP \(bu
hypot fmod max min accept any values as long as they have compatible units\&.
.IP \(bu
abs accepts one argument with any units
.IP \(bu
sqrt accepts any positive argument with any units\&. Returns the
appropriate units\&. If the argument basis unit exponents are not multiples of 2,
the results may be useless\&.
.IP \(bu
exp log log10 accept one argument with no units\&.
.IP \(bu
rand returns a pure number\&.
.IP \(bu
srand accepts any units, but only uses the argument magnitude (ignoring
basis unit exponents)\&. It returns a unit less number\&.
.PP
.PP
Some functions available in \fBexpr\fR are not available in
\fBuexpr\fR\&. Type conversion functions (wide entier double bool) integer
functions (isqrt) number format functions (isordered issubnormal isnan
is normal isinf isfinite) are not implemented\&.
.PP
There are additional functions not normally found in \fBexpr\fR:




























.IP \(bu
\fBhasUnits(expression)\fR returns 0 if the value is a pure number\&.
.CS

hasUnits(3)
.CE
.IP







|


|















|








>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
Trigonometric Functions (sin cos tan) expect a single angle argument (deg rad
\&.\&.\&.)\&.and return a pure number\&.
.IP \(bu
Inverse Trigonometric Functions (asin acos atan) expect a pure
number and return an angle result\&. atan2 expects two arguments with
compatible units\&.
.IP \(bu
Hyperbolic functions (sinh cosh tanh) expect unitless arguments and return a unitless result\&.
.IP \(bu
ceil int floor round round up, toward zero, down and down to the
nearest integer respectively\&. They accept only unitless arguments, and
return an integer valued double precision real number\&.
.IP \(bu
hypot fmod max min accept any values as long as they have compatible units\&.
.IP \(bu
abs accepts one argument with any units
.IP \(bu
sqrt accepts any positive argument with any units\&. Returns the
appropriate units\&. If the argument basis unit exponents are not multiples of 2,
the results may be useless\&.
.IP \(bu
exp log log10 accept one argument with no units\&.
.IP \(bu
rand returns a pure number\&.
.IP \(bu
srand accepts any units, but only uses the argument magnitude (ignoring
basis unit exponents)\&. It returns a unitless number\&.
.PP
.PP
Some functions available in \fBexpr\fR are not available in
\fBuexpr\fR\&. Type conversion functions (wide entier double bool) integer
functions (isqrt) number format functions (isordered issubnormal isnan
is normal isinf isfinite) are not implemented\&.
.PP
There are additional functions not normally found in \fBexpr\fR:
.IP \(bu
\fBsum(var,first_index,last_index,expression)\fR 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
.CS

sum(i,0,4,2^i)
.CE
.IP
will return 31\&.0
.sp
A simple polynomial with coefficients in c
.CS

set c {1 2 3 4 5}
.CE
.CS

set x 0\&.2
.CE
.CS

= sum(i,0,4,c\&.\&.i*x^i)
.CE
.IP
will return 1\&.56
.IP \(bu
\fBhasUnits(expression)\fR returns 0 if the value is a pure number\&.
.CS

hasUnits(3)
.CE
.IP
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714

uexpr 15 psi = in wc = bar
.CE
.IP
returns something like "1\&.0342\&.\&.\&.41 bar", note only the last result unit specifier was used
.CS

uexpr 15*psi= in wc
.CE
.IP
returns something like "415\&.609\&.\&.\&.18 in wc"\&.
Note: "in wc" is inches water column\&.
.TP
\fBltxExpr\fR \fIexpression\fR ?\fIexpression\fR? \&.\&.\&.
\fBltxExpr\fR concatenates its operands separated by a space and
evaluates the expression\&. It returns a list of results\&. The list
always contains the expression formatted for LaTeX, and the result as
a unit expression (which can be used in an expression for uexpr or
ltxExpr)\&. In addition if the expression ends with one or more result unit
specifiers, a magnitude and unit specifier (formatted for LaTeX) is
returned for each result unit specifier\&. If any of 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 no units are specified, a magnitude and
unit specifier (formatted for LaTeX) containing only base units will
be generated\&. ltxExpr is intended for use in the \fBcalc\fR package\&.
.CS

ltxExpr 3*ft=in=cm
.CE
.IP
returns "{3\\cdot \\mathrm{ft}} {36\&.0 in} 36\&.0 {\\mathrm{in}} 91\&.44 {\\mathrm{cm}}"
.sp
\fBltxExpr\fR formats variable names to include symbols and
subscripts\&. The "_" prefix followed by a character will insert a LaTeX
symbol, typically a Greek letter\&. "\&." indicates the following
characters are part of a subscript\&. Multiple subscripts are
allowed\&. For example the variable name:
.CS

_a\&.4
.CE
.IP
generates the following LaTeX: {\\alpha _{\\mathrm{4}}}
.TP
\fBuexpr::unitNames\fR ?\fIglob pattern\fR?
return a list of all units matching the glob pattern\&.
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 unit less constants are returned\&.
.TP
\fBuexpr::newUnit\fR \fIname\fR \fIexpression\fR
\fBuexpr::newUnit\fR adds another unit to the unit array\&. The
expression defines the new unit in terms of any existing base or defined
unit\&. The unit array is common to all contexts within a Tcl
interpreter\&. These changes are global\&. For example a mile is defined
as:







|





|













|




|



















|







689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748

uexpr 15 psi = in wc = bar
.CE
.IP
returns something like "1\&.0342\&.\&.\&.41 bar", note only the last result unit specifier was used
.CS

uexpr 15*psi = in wc
.CE
.IP
returns something like "415\&.609\&.\&.\&.18 in wc"\&.
Note: "in wc" is inches water column\&.
.TP
\fB::uexpr::ltxExpr\fR \fIexpression\fR ?\fIexpression\fR? \&.\&.\&.
\fBltxExpr\fR concatenates its operands separated by a space and
evaluates the expression\&. It returns a list of results\&. The list
always contains the expression formatted for LaTeX, and the result as
a unit expression (which can be used in an expression for uexpr or
ltxExpr)\&. In addition if the expression ends with one or more result unit
specifiers, a magnitude and unit specifier (formatted for LaTeX) is
returned for each result unit specifier\&. If any of 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 no units are specified, a magnitude and
unit specifier (formatted for LaTeX) containing only base units will
be generated\&. ltxExpr is intended for use in the \fBcalc\fR package\&.
.CS

uexpr::ltxExpr 3*ft=in=cm
.CE
.IP
returns "{3\\cdot \\mathrm{ft}} {36\&.0 in} 36\&.0 {\\mathrm{in}} 91\&.44 {\\mathrm{cm}}"
.sp
\fBuexpr::ltxExpr\fR formats variable names to include symbols and
subscripts\&. The "_" prefix followed by a character will insert a LaTeX
symbol, typically a Greek letter\&. "\&." indicates the following
characters are part of a subscript\&. Multiple subscripts are
allowed\&. For example the variable name:
.CS

_a\&.4
.CE
.IP
generates the following LaTeX: {\\alpha _{\\mathrm{4}}}
.TP
\fBuexpr::unitNames\fR ?\fIglob pattern\fR?
return a list of all units matching the glob pattern\&.
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
\fBuexpr::newUnit\fR adds another unit to the unit array\&. The
expression defines the new unit in terms of any existing base or defined
unit\&. The unit array is common to all contexts within a Tcl
interpreter\&. These changes are global\&. For example a mile is defined
as:
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830

\fBuexpr\fR 4*in+5*in=cm
.CE
.IP
Or using ltxExpr, to get the results in cm and feet:
.CS

\fBltxExpr\fR 4*in+3*in=cm=ft
.CE
.IP \(bu
There are no type conversion functions (double entier,
wide)\&. int() exists, and returns a value that looks like an integer,
but it is treated like a real number\&.
.IP \(bu
all functions available to uexpr and friends are in
::uexpr::ufunc\&.
.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"\&.







|



|
<







845
846
847
848
849
850
851
852
853
854
855
856

857
858
859
860
861
862
863

\fBuexpr\fR 4*in+5*in=cm
.CE
.IP
Or using ltxExpr, to get the results in cm and feet:
.CS

\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
::uexpr::ufunc\&.
.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"\&.
838
839
840
841
842
843
844


845
846
847
848
849
850
851
However
.CS

3*ft/3*in == 0\&.083333\&.\&.\&.*ft^2
.CE
.IP
Probably not what was desired\&.


.IP \(bu
Numbers can be raised to a power using "**" or "^"\&. Currently
"**" and "^" work as shown in Abramowitz and Stegun "Handbook of Mathematical Functions \&.\&.\&." rather than Tcl's \fBexpr\fR\&.
.CS

uexpr -2^4^2
.CE







>
>







871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
However
.CS

3*ft/3*in == 0\&.083333\&.\&.\&.*ft^2
.CE
.IP
Probably not what was desired\&.
.IP \(bu
Comparisons can be chained\&. 1<a<3 is true if the value in a is between 1 and 3\&.
.IP \(bu
Numbers can be raised to a power using "**" or "^"\&. Currently
"**" and "^" work as shown in Abramowitz and Stegun "Handbook of Mathematical Functions \&.\&.\&." rather than Tcl's \fBexpr\fR\&.
.CS

uexpr -2^4^2
.CE
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878


879
880
881
882
883
884
885
expr 0-2**4**2
.CE
.IP
returns -65536
.PP
.SH EXAMPLES
.SS "MASS AND FORCE UNITS"
In US Customary units pounds (lb) can refer to a mass or the force
it exerts in a 1 g gravity field\&. This causes all sorts of mysterious
gc factors (typically about 32\&.174) to appear in formulas\&. In the
\fBuexpr\fR package, all US Customary mass units have an "m"
suffix (lbm ozm \&.\&.\&.) and all US Customary force units have an "f" suffix (lbf ozf
\&.\&.\&.) the base unit name (lb, oz \&.\&.\&.) is not defined on purpose, so
its use will cause an undefined value error\&. This avoids the inevitable
units mismatch which occurs further on in the calculation\&.


.PP
Metric units (SI) have managed to avoid this problem (mass is
Kg or gram, force is newton N), although I've started to see Kg (and
sometimes Kgf) show up as a force unit in some places\&. It was good
while it lasted\&.
.SS "TEMPERATURE AND PRESSURE UNITS"
\fBuexpr\fR only does scaling unit conversions automatically\&. In







|
|
|

|
|
|
|
>
>







899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
expr 0-2**4**2
.CE
.IP
returns -65536
.PP
.SH EXAMPLES
.SS "MASS AND FORCE UNITS"
In US Customary units pounds (lb) can refer to a mass or the force it
exerts in a 1 g gravity field\&. This causes all sorts of mysterious gc
factors (typically about 32\&.174) to appear in formulas\&. In the
\fBuexpr\fR package, all US Customary mass units have an "m"
suffix (lbm ozm \&.\&.\&.) and all US Customary force units have an "f"
suffix (lbf \&.\&.\&.) the base unit name (lb, \&.\&.\&.) is not defined on
purpose, so its use will cause an undefined value error\&. This avoids
the inevitable units mismatch which occurs further on in the
calculation\&. oz is actually defined as a volume unit\&. while ozm is a
mass unit\&.
.PP
Metric units (SI) have managed to avoid this problem (mass is
Kg or gram, force is newton N), although I've started to see Kg (and
sometimes Kgf) show up as a force unit in some places\&. It was good
while it lasted\&.
.SS "TEMPERATURE AND PRESSURE UNITS"
\fBuexpr\fR only does scaling unit conversions automatically\&. In
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
however the residual exponent after calculating the answer in feet is
-0\&.000001, within the -0\&.01 to 0\&.01 range for it to be ignored, giving
the exact result 27\&.0*ft\&.
.PP
Formulas using customary units may end up with fractional
exponents\&. The calculation of liquid flow rate through a valve above
is an example\&.  The procedure to format the formula for self
consistent units, results in all calculations being done with unit
less values, so there is no problem with fractional basis unit
exponents\&.
.PP
Some formulas in fluid mechanics and heat transfer raise unit less
numbers such as Reynolds number, Prandtl number and Nusselt number to
odd decimal fractional powers\&. However as these are unit less
parameters, there is no problem with fractional exponents on basis
units\&.
.PP
Another example is modeling a compressor as an isentropic or
polytropic process, where volume ratios are raised to powers between
1\&.4 and 1\&.1 (isentropic volume exponent)\&. Once again the volume ratio
is unit less, and there are no fractional exponents on the basis units\&.
.SH KEYWORDS
Calculations, uexpr
.SH COPYRIGHT
.nf
Copyright (c) 2021 J\&.D Bruchie (BSD License)

.fi







|
|
<

|

|






|







1059
1060
1061
1062
1063
1064
1065
1066
1067

1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
however the residual exponent after calculating the answer in feet is
-0\&.000001, within the -0\&.01 to 0\&.01 range for it to be ignored, giving
the exact result 27\&.0*ft\&.
.PP
Formulas using customary units may end up with fractional
exponents\&. The calculation of liquid flow rate through a valve above
is an example\&.  The procedure to format the formula for self
consistent units, results in all calculations being done with unitless
values, so there is no problem with fractional basis unit exponents\&.

.PP
Some formulas in fluid mechanics and heat transfer raise unitless
numbers such as Reynolds number, Prandtl number and Nusselt number to
odd decimal fractional powers\&. However as these are unitless
parameters, there is no problem with fractional exponents on basis
units\&.
.PP
Another example is modeling a compressor as an isentropic or
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 J\&.D Bruchie (BSD License)

.fi

Changes to uexpr.tcl.

1
2
3
4
5
6
7
8
9
10
11
12
13











14
15
16
17
18
19
20
#
# expressions with units
#
# Copyright 2020 James David Bruchie
# see license file for details
#
# math with units
#
# Internally a value with units (uval, uvalue) is a list,
# the first element is a magnitude,
# the rest are exponents on the base dimensions:
#   Length Mass Time Charge Degree ...
#











# 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)



|




|




>
>
>
>
>
>
>
>
>
>
>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#
# expressions with units
#
# Copyright 2020-2024 James David Bruchie
# see license file for details
#
# math with units
#
# 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 dict or named variables).
# Results from uexpr are returned as uvalues.
#
# currently arrays are nested lists of uvals or uvalues
#   no other structure
#   no bounds checking
#
#
# perhaps:
# 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)
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
# operators specific to uexpr:
#  $ following name is a variable in the calling context
#  : following name is a unit
#  .. separates a vector or array name from an index expression
#     an index expression can be a single value or a list,
#     such as (i,j)  or  (1,i,2)
# ++ is a line break in an equation at a + operator
#      it is converted to \U229E (boxed +) then parsed to +CR


#      +CR evaluates like +


#      LaTeX generates the line break at a binary + operator
#  -- is a line break at a subtract
#      it is converted to \U229E (boxed +) followed by \U229F (boxed -)
#      then parsed to +CR -CR
#      -CR evaluates like negate
#      For LaTeX it acts like neg, but inserts \U229F instead of minus
#      The LaTeX generator for ++ (actually +CR)
#       checks for a \U229F as the first character in the right operand
#       and generates a line break at a binary - operator
#
# 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)







|
>
>
|
>
>
|

|
|
|
<
|
<
|







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
# operators specific to uexpr:
#  $ following name is a variable in the calling context
#  : following name is a unit
#  .. separates a vector or array name from an index expression
#     an index expression can be a single value or a list,
#     such as (i,j)  or  (1,i,2)
# ++ is a line break in an equation at a + operator
#      it is converted to \U229E (boxed +) then parsed:
#         if left op is CR+,  return left op with + and right op appended
#         otherwise return {CR+ leftOp + rightOp}
#      +CR evaluates to accumulate a value from its list:
#          set result to first op
#          then apply next op and operand to sum, until run out of op/value pairs
#      LaTeX generates an array with one operand per line, separated by given operators
#  -- is a line break at a subtract
#      it is converted to \U229F (boxed -)
#      then parsed:
#          if left op is CR+, return left op with - and right op appended

#          otherwise return {CR+ leftOp - rightOp}

#      -CR is handled by +CR for evaluation and generating LaTeX
#
# 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)
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


namespace eval ::uexpr {
    namespace export uexpr ltxExpr uset

    # always reinitalize memoize dict when source code changes!
    variable exprs [dict create]


    
    namespace eval eval {namespace path [namespace parent]}

    namespace eval parseOp {
	namespace path [namespace parent]
	namespace export *

    }

    namespace eval parseValue {
	namespace path [namespace parent]
	namespace export *

    }

    namespace eval ltx {namespace path [namespace parent]}
















    namespace eval set {
	# this namespace is used by uset to resolve the destination
	# variable reference
	# It is effectively the same as ::uexpr::eval
	# but with name and arrayAccess procs redefined to save a value
	# instead of retrieve one







>
>






>





>


|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







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


namespace eval ::uexpr {
    namespace export uexpr ltxExpr uset

    # always reinitalize memoize dict when source code changes!
    variable exprs [dict create]
    variable rprec
    variable lprec
    
    namespace eval eval {namespace path [namespace parent]}

    namespace eval parseOp {
	namespace path [namespace parent]
	namespace export *
	namespace upvar ::uexpr  lprec lprec  rprec rprec
    }

    namespace eval parseValue {
	namespace path [namespace parent]
	namespace export *
	namespace upvar ::uexpr  lprec lprec  rprec rprec
    }

    namespace eval ltx {
	namespace path [namespace parent]

	# map operators to latex symbols
	# mostly for compares
	variable ltxSym
	array set ltxSym {
	    \U2248 =
	    \U2249 \\neq
	    < <
	    \U2264 \\leq
	    \U2265 \\geq
	    > >
	}
	
    }

    namespace eval set {
	# this namespace is used by uset to resolve the destination
	# variable reference
	# It is effectively the same as ::uexpr::eval
	# but with name and arrayAccess procs redefined to save a value
	# instead of retrieve one
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
    # does not seem to work with ensembles
    set sys(symmap) [list "==" \U2248  \
			 "!=" \U2249  "<>" \U2249  \
			 "<=" \U2264  "=<" \U2264  \
			 ">=" \U2265  "=>" \U2265  \
			 ** ^  \
			 ++ \U229E  \
			 -- \U229E\U229F  \
			 : \U2192  \
			 .. \U21BC]
    # operator symbol chars (and %)
    set sys(opchar) " \n\r\t()$\U2192\U21BC%^*/<>=+,-\U2248\U2249\U2264\U2265\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]







|







345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
    # does not seem to work with ensembles
    set sys(symmap) [list "==" \U2248  \
			 "!=" \U2249  "<>" \U2249  \
			 "<=" \U2264  "=<" \U2264  \
			 ">=" \U2265  "=>" \U2265  \
			 ** ^  \
			 ++ \U229E  \
			 -- \U229F  \
			 : \U2192  \
			 .. \U21BC]
    # operator symbol chars (and %)
    set sys(opchar) " \n\r\t()$\U2192\U21BC%^*/<>=+,-\U2248\U2249\U2264\U2265\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]
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515

    proc parse {str} {
	# mostly sets up context for actual parsing by parseValue
	# note these variables are upvar'd from here by all called procs that need them
	# lvl points to the stack frame containg the parser context for other parser procs
	#    perhaps rename lvl to parserContext ???

	# linking the precedence values here
	# may change to allow alternate precedence schemes
	variable lprec
	variable rprec
	variable sys
	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 to following operators
	applyOp $lvl 1 [parseValue [nextToken $lvl] $lvl 1]







<
<
<
<
<







529
530
531
532
533
534
535





536
537
538
539
540
541
542

    proc parse {str} {
	# mostly sets up context for actual parsing by parseValue
	# note these variables are upvar'd from here by all called procs that need them
	# lvl points to the stack frame containg the parser context for other parser procs
	#    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 to following operators
	applyOp $lvl 1 [parseValue [nextToken $lvl] $lvl 1]
577
578
579
580
581
582
583
584
585
586
587
588
589


590
591
592
593
594
595
596
    setOp , 15 16

    # If "(" appears as an operator the preceding word is a function name
    # the following group is its parameter list
    setOp func 60 ""

    # comparisons
    setOp \U2248 20 20
    setOp \U2249 20 20
    setOp < 20 20
    setOp \U2264 20 20
    setOp \U2265 20 20
    setOp > 20 20



    setOp + 30 31
    setOp - 30 31
    setOp +CR 28 29
    # same as negate
    setOp -CR "" 35








|
|
|
|
|
|
>
>







604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
    setOp , 15 16

    # If "(" appears as an operator the preceding word is a function name
    # the following group is its parameter list
    setOp func 60 ""

    # comparisons
    setOp \U2248 20 21
    setOp \U2249 20 21
    setOp < 20 21
    setOp \U2264 20 21
    setOp \U2265 20 21
    setOp > 20 21
    # generic chained compare operator
    setOp CC 20 21

    setOp + 30 31
    setOp - 30 31
    setOp +CR 28 29
    # same as negate
    setOp -CR "" 35

675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
    # operators to the left of their single operand
    # note the proc name within parseValue is the token,
    # however the name in the resulting nested list is different
    # to distinguish them from binary operators with the same token
    # (but in different context)

    proc parseValue::- {lvl prec} {
	upvar $lvl rprec rprec
	# fairly generic for any operator to left of its operand
	# however there is only "-" for now as "(" and  unary + are "special"
	# note precedence is max of current context precedence
	# and operators precedence
	applyOp $lvl $prec \
	    [list \
		 neg \
		 [parseValue [nextToken $lvl] \
		      $lvl \
		      [::tcl::mathfunc::max $prec $rprec(neg)]]]
    }


    proc parseValue::\U229F {lvl prec} {
	# boxed - should only follow boxed plus (as part of --)
	# evaluates like negate a negate
	# displays as line break at subtract
	upvar $lvl rprec rprec
	applyOp $lvl $prec \
	    [list -CR \
		 [parseValue [nextToken $lvl] $lvl \
		      [::tcl::mathfunc::max $prec $rprec(-CR)]]]
    }


    

    proc parseValue::+ {lvl prec} {
	# unary +
	upvar $lvl rprec rprec
	# get value, but with adjusted precedence
	# currently not adding a {pos ...} wrapper
	tailcall parseValue \
	    [nextToken $lvl] \
	    $lvl \
	    [::tcl::mathfunc::max $prec $rprec(pos)]
    }


    proc parseValue::\( {lvl prec} {
	# start sub expression
	upvar $lvl rprec rprec
	dm {start parsing subexpression}
	# no need to adjust precedence as ")" always ends a sub expression
	set val [applyOp $lvl $rprec(subexpr) \
		     [list \
			  subexpr \
			  [parseValue [nextToken $lvl] $lvl $rprec(subexpr)]]]
	# if it was a list complain for now !!!







|













<
<
<
<
<
<
<
<
<
<
<
<
<
<


|











|







704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724














725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
    # operators to the left of their single operand
    # note the proc name within parseValue is the token,
    # however the name in the resulting nested list is different
    # to distinguish them from binary operators with the same token
    # (but in different context)

    proc parseValue::- {lvl prec} {
	variable rprec
	# fairly generic for any operator to left of its operand
	# however there is only "-" for now as "(" and  unary + are "special"
	# note precedence is max of current context precedence
	# and operators precedence
	applyOp $lvl $prec \
	    [list \
		 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] \
	    $lvl \
	    [::tcl::mathfunc::max $prec $rprec(pos)]
    }


    proc parseValue::\( {lvl prec} {
	# start sub expression
	variable rprec
	dm {start parsing subexpression}
	# no need to adjust precedence as ")" always ends a sub expression
	set val [applyOp $lvl $rprec(subexpr) \
		     [list \
			  subexpr \
			  [parseValue [nextToken $lvl] $lvl $rprec(subexpr)]]]
	# if it was a list complain for now !!!
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
    }


    proc parseValue::\) {lvl prec} {
	# ) is normally an operator,
	# however this could be the end of a function call with no operands
	# if not within a function call
	upvar $lvl  rprec rprec
	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







|







797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
    }


    proc parseValue::\) {lvl prec} {
	# ) is normally an operator,
	# however this could be the end of a function call with no operands
	# if not within a function call
	variable rprec
	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
854
855
856
857
858
859
860
861

862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879






































880
881
882
883
884
885
886



































887
888
889
890
891
892
893
894
895
896
897
898
899

900
901
902
903
904
905
906
    #   lprec of operator just applied
    
    # define binary and right operators
    # all operators that are to the right of at least one of their operands

    proc parseBinaryOp {op lvl prec lvalue} {
	# handles most common binary operators
	upvar $lvl  rprec rprec lprec lprec

	dm {parseBinaryOp $op $lvl  prec: $prec  lvalue:$lvalue}
	# if operator left prec is greater than or equal to subexpression prec
	if {$prec <= $lprec($op)} {
	    # apply operator (and its right subexpression value)
	    # to the current subexpression
	    set lvalue [list \
			    $op \
			    $lvalue \
			    [parseValue [nextToken $lvl] $lvl $rprec($op)]]
	    dm {parseBinaryOp $op used new value: $lvalue}
	} else {
	    # reuse op token and return left subexpression value
	    reuseToken $lvl
	    dm {parseBinaryOp $op not used}
	}
	list $lvalue $lprec($op)
    }
	






































    interp alias  {} parseOp::\U2248  {} ::uexpr::parseBinaryOp \U2248
    interp alias  {} parseOp::\U2249  {} ::uexpr::parseBinaryOp \U2249
    interp alias  {} parseOp::<  {} ::uexpr::parseBinaryOp <
    interp alias  {} parseOp::\U2264  {} ::uexpr::parseBinaryOp \U2264
    interp alias  {} parseOp::\U2265  {} ::uexpr::parseBinaryOp \U2265
    interp alias  {} parseOp::>  {} ::uexpr::parseBinaryOp >




































    interp alias {} parseOp::\U229E  {} ::uexpr::parseBinaryOp +CR
    #interp alias {} parseOp::\U229F  {} ::uexpr::parseBinaryOp -CR
    interp alias  {} parseOp::+  {} ::uexpr::parseBinaryOp +
    interp alias  {} parseOp::-  {} ::uexpr::parseBinaryOp -
    interp alias  {} parseOp::*  {} ::uexpr::parseBinaryOp *

    #interp alias {} parseOp::\U21BC {} ::uexpr::parseBinaryOp arrayAccess
    
    proc parseOp::imul {lvl prec lvalue} {
	# implied multiply (from a value followed by another value
	# with no operator between)
	# almost the same as parseBinaryOp
	upvar $lvl  rprec rprec lprec lprec

	dm {parseOp::imul $lvl  prec: $prec  lvalue:$lvalue}
	# if operator left prec is less than subexpression prec
	if {$prec <= $lprec(imul)} {
	    set lvalue [list \
			    imul \
			    $lvalue \
			    [parseValue [nextToken $lvl] $lvl $rprec(imul)]]







|
>


















>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
|
|
|
|
|

>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
|
<
<
<
|
<





|
>







869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977



978

979
980
981
982
983
984
985
986
987
988
989
990
991
992
    #   lprec of operator just applied
    
    # define binary and right operators
    # all operators that are to the right of at least one of their operands

    proc parseBinaryOp {op lvl prec lvalue} {
	# handles most common binary operators
	variable rprec
	variable lprec
	dm {parseBinaryOp $op $lvl  prec: $prec  lvalue:$lvalue}
	# if operator left prec is greater than or equal to subexpression prec
	if {$prec <= $lprec($op)} {
	    # apply operator (and its right subexpression value)
	    # to the current subexpression
	    set lvalue [list \
			    $op \
			    $lvalue \
			    [parseValue [nextToken $lvl] $lvl $rprec($op)]]
	    dm {parseBinaryOp $op used new value: $lvalue}
	} else {
	    # reuse op token and return left subexpression value
	    reuseToken $lvl
	    dm {parseBinaryOp $op not used}
	}
	list $lvalue $lprec($op)
    }
	
    interp alias  {} parseOp::+  {} ::uexpr::parseBinaryOp +
    interp alias  {} parseOp::-  {} ::uexpr::parseBinaryOp -
    interp alias  {} parseOp::*  {} ::uexpr::parseBinaryOp *


    proc parseChainedCompare {op lvl prec lvalue} {
	# parse chained compares
	# allow a<b<c<d or even a<b<=c>d<d
	# the subexpression between compares is used in both compares
	# the results of the compares are "anded", so all must be true for a true result
	variable rprec
	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(+CR)]
	    } else {
		# apply CC (and its operator and right subexpression value)
		# to the current subexpression
		set lvalue [list \
				CC \
				$lvalue \
				[list funcName $op]  \
				[parseValue [nextToken $lvl] $lvl $rprec(CC)]]
		dm {parseChainedCompare used new value: $lvalue}
	    }
	} else {
	    # reuse op token and return left subexpression value
	    reuseToken $lvl
	    dm {parseChainedCompare ${op} not used}
	}
	list $lvalue $lprec(CC)
    }

    
    interp alias  {} parseOp::\U2248  {} ::uexpr::parseChainedCompare \U2248
    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
	variable rprec
	variable lprec
	dm {parse+CR $op $lvl  prec: $prec  lvalue:$lvalue}
	# if operator left prec is greater than or equal to subexpression prec
	if {$prec <= $lprec(+CR)} {
	    # if left operand is +CR
	    if {"+CR" eq [lindex $lvalue 0]} {
		# add to existing +CR list
		lappend lvalue [list funcName $op] [parseValue [nextToken $lvl] $lvl $rprec(+CR)]
	    } else {
		# apply +CR (and its operator and right subexpression value)
		# to the current subexpression
		set lvalue [list \
				+CR \
				$lvalue \
				[list funcName $op]  \
				[parseValue [nextToken $lvl] $lvl $rprec(+CR)]]
		dm {parse+CR used new value: $lvalue}
	    }
	} else {
	    # reuse op token and return left subexpression value
	    reuseToken $lvl
	    dm {parse+CR ${op}CR not used}
	}
	list $lvalue $lprec(+CR)
    }
    
    interp alias {} parseOp::\U229E  {} ::uexpr::parse+CR +
    interp alias {} parseOp::\U229F  {} ::uexpr::parse+CR -



    

    
    proc parseOp::imul {lvl prec lvalue} {
	# implied multiply (from a value followed by another value
	# with no operator between)
	# almost the same as parseBinaryOp
	variable rprec
	variable lprec
	dm {parseOp::imul $lvl  prec: $prec  lvalue:$lvalue}
	# if operator left prec is less than subexpression prec
	if {$prec <= $lprec(imul)} {
	    set lvalue [list \
			    imul \
			    $lvalue \
			    [parseValue [nextToken $lvl] $lvl $rprec(imul)]]
922
923
924
925
926
927
928
929

930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950

951
952
953
954
955
956
957
    }

    
    proc parseOp::/ {lvl prec lvalue} {
	# division, handles removing redundant () on both operands
	#  which may be a mistake if usng inline divide output,
	#     instead of numerator over denominator ???
	upvar $lvl  rprec rprec  lprec lprec

	dm {parse / $lvl $lvalue}
	# if operator prec is less than left subexpression prec
	if {$prec > $lprec(/)} {
	    # reuse op token and return left subexpression value
	    reuseToken $lvl
	    list $lvalue $lprec(/)
	} else {
	    # divide delimits both operands so strip subexpr from both operands
	    # ??? move this to formating code???
	    list [list  \
		      "/"  \
		      [stripSubexpr $lvalue]  \
		      [stripSubexpr \
			   [parseValue [nextToken $lvl] $lvl $rprec(/)]]] \
		$lprec(/)
	}
    }
    

    proc parseOp::^ {lvl prec lvalue} {
	upvar $lvl  rprec rprec  lprec lprec

	dm {parse ^ $lvl $prec $lvalue}
	# if operator prec is less than left subexpression prec
	if {$prec > $lprec(^)} {
	    # reuse op token and return left subexpression value
	    reuseToken $lvl
	    list $lvalue $lprec(^)
	} else {







|
>




















|
>







1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
    }

    
    proc parseOp::/ {lvl prec lvalue} {
	# division, handles removing redundant () on both operands
	#  which may be a mistake if usng inline divide output,
	#     instead of numerator over denominator ???
	variable rprec
	variable lprec
	dm {parse / $lvl $lvalue}
	# if operator prec is less than left subexpression prec
	if {$prec > $lprec(/)} {
	    # reuse op token and return left subexpression value
	    reuseToken $lvl
	    list $lvalue $lprec(/)
	} else {
	    # divide delimits both operands so strip subexpr from both operands
	    # ??? move this to formating code???
	    list [list  \
		      "/"  \
		      [stripSubexpr $lvalue]  \
		      [stripSubexpr \
			   [parseValue [nextToken $lvl] $lvl $rprec(/)]]] \
		$lprec(/)
	}
    }
    

    proc parseOp::^ {lvl prec lvalue} {
	variable rprec
	variable lprec
	dm {parse ^ $lvl $prec $lvalue}
	# if operator prec is less than left subexpression prec
	if {$prec > $lprec(^)} {
	    # reuse op token and return left subexpression value
	    reuseToken $lvl
	    list $lvalue $lprec(^)
	} else {
975
976
977
978
979
980
981
982

983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002

1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014

1015
1016

1017
1018
1019
1020
1021
1022
1023
    # ??? may want this to be an error instead ???
    interp alias  {} parseOp::=  {} ::uexpr::parseBinaryOp =


    proc parseOp::, {lvl prec lvalue} {
	# handle lists of values
	# currently used for function arguments, vectors and arrays
	upvar $lvl  lprec lprec  rprec rprec

	dm {parseOp::, $lvl $prec $lvalue}
	if {$prec > $lprec(,)} {
	    reuseToken $lvl
	} else {
	    # build a list
	    # if lvalue is not already a list start one
	    if {"vlist" ne [lindex $lvalue 0]} {
		dm {parseOp::, making lvalue: $lvalue a list}
		set lvalue [list "vlist" $lvalue]
	    }
	    # append next value to the list
	    lappend lvalue [applyOp $lvl $rprec(,) \
				[parseValue [nextToken $lvl] $lvl $rprec(,)]]
	}
	list $lvalue $lprec(,)
    }


    proc parseOp::\) {lvl prec lvalue} {
	upvar $lvl  lprec lprec  rprec rprec

	# keep ) token in use until prec is less than ")"
	if {$prec < $lprec(\)) && [lindex $lvalue 0] ni {func list subexpr}} {
	    error "no matching '(' or function call for ')' for: $lvalue"
	}
	dm {parseop::\)  lvl: $lvl  prec: $prec}
	# always reuse token, it is used when resolving "("
	reuseToken $lvl
	# always return left value
	list $lvalue $lprec(\))
    }



    proc parseOp::\( {lvl prec lvalue} {
	upvar $lvl  lprec lprec  rprec rprec

	dm {parsing "("  lvl: $lvl  prec: $prec}
	# "(" in the context of an operator
	# if the left value is a valid function name (in ufunc)
	if {("name" eq [lindex $lvalue 0])
	    && ("" ne [info commands ::uexpr::ufunc::[lindex $lvalue 1]])} {
	    # this is a function call
	    # get function argument list







|
>



















|
>












>

|
>







1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
    # ??? may want this to be an error instead ???
    interp alias  {} parseOp::=  {} ::uexpr::parseBinaryOp =


    proc parseOp::, {lvl prec lvalue} {
	# handle lists of values
	# currently used for function arguments, vectors and arrays
	variable lprec
	variable rprec
	dm {parseOp::, $lvl $prec $lvalue}
	if {$prec > $lprec(,)} {
	    reuseToken $lvl
	} else {
	    # build a list
	    # if lvalue is not already a list start one
	    if {"vlist" ne [lindex $lvalue 0]} {
		dm {parseOp::, making lvalue: $lvalue a list}
		set lvalue [list "vlist" $lvalue]
	    }
	    # append next value to the list
	    lappend lvalue [applyOp $lvl $rprec(,) \
				[parseValue [nextToken $lvl] $lvl $rprec(,)]]
	}
	list $lvalue $lprec(,)
    }


    proc parseOp::\) {lvl prec lvalue} {
	variable lprec
	variable rprec
	# keep ) token in use until prec is less than ")"
	if {$prec < $lprec(\)) && [lindex $lvalue 0] ni {func list subexpr}} {
	    error "no matching '(' or function call for ')' for: $lvalue"
	}
	dm {parseop::\)  lvl: $lvl  prec: $prec}
	# always reuse token, it is used when resolving "("
	reuseToken $lvl
	# always return left value
	list $lvalue $lprec(\))
    }


    # handles function calls and implied multiply of the following subexperssion
    proc parseOp::\( {lvl prec lvalue} {
	variable lprec
	variable rprec
	dm {parsing "("  lvl: $lvl  prec: $prec}
	# "(" in the context of an operator
	# if the left value is a valid function name (in ufunc)
	if {("name" eq [lindex $lvalue 0])
	    && ("" ne [info commands ::uexpr::ufunc::[lindex $lvalue 1]])} {
	    # this is a function call
	    # get function argument list
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059



1060
1061

1062
1063
1064
1065
1066
1067
1068
	    # return a list of: func <func name> <list of argument values>
	    # append list contents to function call
	    # return the func left precedence
	    dm {func call nel: list  [list  \
					  func \
					  [list funcName ] \
					  {*}$ops]  $lprec(func)}
	    # now apply it to any trailing operators at higher precedence
	    list [list  \
		      func \
		      [list funcName [lindex $lvalue 1]] \



		      {*}$ops] \
		$lprec(func)

	} else {
	    # this is an implied multiply with a subexpression on the right
	    # '(' will be handled as valueOp
	    dm {implied multiply by a subexpression}
	    if {$prec < $lprec(imul)} {
		# implied multiply prec is higher
		# evaluate the subexpr as its right op,







|
<
|
|
>
>
>
|

>







1141
1142
1143
1144
1145
1146
1147
1148

1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
	    # return a list of: func <func name> <list of argument values>
	    # append list contents to function call
	    # return the func left precedence
	    dm {func call nel: list  [list  \
					  func \
					  [list funcName ] \
					  {*}$ops]  $lprec(func)}
	    # check if there is special function parsing logic

	    # the ::uexpr::funcParse namespace unknown function handles generic functions
	    set fname [lindex $lvalue 1]
	    # return a list of the function a a sub expression and its left precedence
	    # Tcl 9 namespace path logic could simplify this
	    list \
		[namespace eval ::uexpr::funcParse [list ::uexpr::funcParse::$fname {*}$ops]]  \
		$lprec(func)
	    # now apply it to any trailing operators at higher precedence
	} else {
	    # this is an implied multiply with a subexpression on the right
	    # '(' will be handled as valueOp
	    dm {implied multiply by a subexpression}
	    if {$prec < $lprec(imul)} {
		# implied multiply prec is higher
		# evaluate the subexpr as its right op,
1077
1078
1079
1080
1081
1082
1083































1084
1085
1086
1087
1088
1089

1090
1091
1092
1093
1094
1095
1096
		# defer evaluation
		reuseToken $lvl
		list $lvalue $lprec(imul) 
	    }
	}
    }

































    proc parseOp::\U21BC {lvl prec lvalue} {
	# arrayAccess
	# capture lvalue as a vector name
	# need to allow a list value as left operator too !!!
	upvar $lvl  rprec rprec lprec lprec

	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







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>





|
>







1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
		# defer evaluation
		reuseToken $lvl
		list $lvalue $lprec(imul) 
	    }
	}
    }


    namespace eval funcParse {
	# special parsing for complex functions

	# unknown parses the generic function
	namespace unknown {
	    apply {{name args} {
		#puts "funcParse unknown $name $args"
		list  \
		    func  \
		    [list funcName [namespace tail $name]]  \
		    {*}$args}
	    }
	}

	# sum function
	# sum(itteration vatr, start, end, expr)

	proc sum {args} {
	    if {4 != [llength $args]} {
		error "sum expects an iteration variable, first and last values and an expression"
	    }
	    lassign $args var start end expr
	    if {"name" ne [lindex $var 0]} {
		error "expecting iteration index variable name"
	    }
	    lset var 0 "funcName"
	    list func [list funcName "sum"] $var $start $end [list funcName [::uexpr::tclize $expr]]
	}	
    }


    proc parseOp::\U21BC {lvl prec lvalue} {
	# arrayAccess
	# capture lvalue as a vector name
	# need to allow a list value as left operator too !!!
	variable rprec
	variable lprec
	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
1107
1108
1109
1110
1111
1112
1113

1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
		set rvalue [list index [lindex $rvalue 1]]
	    }
	} else {
	    set rvalue [list index $rvalue]
	}
	# apply operator (and its right subexpression value)
	# to the current subexpression

	set lvalue [list arrayAccess \
			[list funcName [lindex $lvalue 1]] \
			$rvalue]
	dm {parsearrayAccess used new value: $lvalue}
	list $lvalue $lprec(\U21BC)
    }
	



    proc parseOp:: {lvl prec lvalue} {
	# end of token list returns ""
	# returning lvalue effectively ends token processing
	upvar $lvl  lprec lprec
	dm {parse !!blank!! $lvl $prec $lvalue}
	list  $lvalue $lprec()
    }
    
    
    proc unknownOp {x op lvl prec lvalue} {
	dm {unknownOp $x $op $lvl $prec $lvalue}







>













|







1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
		set rvalue [list index [lindex $rvalue 1]]
	    }
	} else {
	    set rvalue [list index $rvalue]
	}
	# apply operator (and its right subexpression value)
	# to the current subexpression
	# note the array name operand is funcName as that evaluates to the name string
	set lvalue [list arrayAccess \
			[list funcName [lindex $lvalue 1]] \
			$rvalue]
	dm {parsearrayAccess used new value: $lvalue}
	list $lvalue $lprec(\U21BC)
    }
	



    proc parseOp:: {lvl prec lvalue} {
	# end of token list returns ""
	# returning lvalue effectively ends token processing
	variable lprec
	dm {parse !!blank!! $lvl $prec $lvalue}
	list  $lvalue $lprec()
    }
    
    
    proc unknownOp {x op lvl prec lvalue} {
	dm {unknownOp $x $op $lvl $prec $lvalue}
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
    #   the function name (used for named functions)
    #   a list of function operands in the same order as the input string


    # The functions in uexpr are implemented as procs in ::uexpr::ufunc::
    # The function gets all the uvals within parenthesis as uvals
    # on its command line
    # it must return a single uvalue.
    # The function must check that it gets the correct number
    # and type of parameters.
    # uFunc is called when the closing ")" of the parameter list is found

    proc eval::func {func args} {
	dm {uexpr::eval::func level [info level]  $func $args}
	#[namespace which ::uexpr::ufunc::$func] {*}$args







|







1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
    #   the function name (used for named functions)
    #   a list of function operands in the same order as the input string


    # The functions in uexpr are implemented as procs in ::uexpr::ufunc::
    # The function gets all the uvals within parenthesis as uvals
    # on its command line
    # it must return a single uval.
    # The function must check that it gets the correct number
    # and type of parameters.
    # uFunc is called when the closing ")" of the parameter list is found

    proc eval::func {func args} {
	dm {uexpr::eval::func level [info level]  $func $args}
	#[namespace which ::uexpr::ufunc::$func] {*}$args
1229
1230
1231
1232
1233
1234
1235


1236
1237
1238
1239


1240
1241
1242
1243
1244
1245
1246
	    append str " \\right ) "
	} else {
	    append str "() "
	}
    }
    



    interp alias {} eval::funcName  {} return -level 0
    interp alias {}  ltx::funcName  {} return -level 0

    # create a new user defined function in uexpr::ufunc


    # the function definition is a single expression
    # The arguments will be received in the order they are listed in
    # the definition
    # Any missing arguments will be looked up as variables in
    # the calling context of the uExpr (at the time the function is used)
    # !!! ugly and fragile:
    #   assumes stack level of uExpr context is 3 down stack !!! )







>
>




>
>







1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
	    append str " \\right ) "
	} else {
	    append str "() "
	}
    }
    

    # these procs just return their argument
    # it gets weird if there is more than one argument
    interp alias {} eval::funcName  {} return -level 0
    interp alias {}  ltx::funcName  {} return -level 0

    # create a new user defined function in uexpr::ufunc
    #   Like units, the function is defined for the Tcl interpreter
    #   including any variable values read during function definition
    # the function definition is a single expression
    # The arguments will be received in the order they are listed in
    # the definition
    # Any missing arguments will be looked up as variables in
    # the calling context of the uExpr (at the time the function is used)
    # !!! ugly and fragile:
    #   assumes stack level of uExpr context is 3 down stack !!! )
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
    # parameters have fixed values defined in the context of the function
    # definition.

    proc func {name paramNames uexpr} {
	# generate the proc to be called in ufunc namespace
	# returns a uval
	set cl [info level]
	set lvl #$cl
	set callingLevel #[incr cl -1]
	variable lprec
	variable rprec
	variable sys
	# finalize non local variable and unit name references
	# and generate Tcl code for the function
	set ecmd [tclize [resolveNonLocals \
			      $callingLevel \
			      $lvl \
			      $paramNames \
			      [parse $uexpr]]]

	# define the function proc in ::uexpr::ufunc
	proc ::uexpr::ufunc::$name  {args} [format {
	    #puts "level [info level]  args $args"
	    set paramNames {%1$s}







|

<
<
<




<







1392
1393
1394
1395
1396
1397
1398
1399
1400



1401
1402
1403
1404

1405
1406
1407
1408
1409
1410
1411
    # parameters have fixed values defined in the context of the function
    # definition.

    proc func {name paramNames uexpr} {
	# generate the proc to be called in ufunc namespace
	# returns a uval
	set cl [info level]
	#set lvl #$cl
	set callingLevel #[incr cl -1]



	# finalize non local variable and unit name references
	# and generate Tcl code for the function
	set ecmd [tclize [resolveNonLocals \
			      $callingLevel \

			      $paramNames \
			      [parse $uexpr]]]

	# define the function proc in ::uexpr::ufunc
	proc ::uexpr::ufunc::$name  {args} [format {
	    #puts "level [info level]  args $args"
	    set paramNames {%1$s}
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
				    [lmap x [lrange {%s} [llength $args] end] {
					::uexpr::ltx::name $x
				    }]]
	} $name $paramNames]
    }


    proc resolveNonLocals {callingLevel lvl locals etl} {
	# recurse through the expression tree list (etl)
	# resolving all nonlocal variables to uvals
	# return a modified copy of the etl
	upvar $lvl sys sys
	set cmd [lindex $etl 0]
	if {$cmd in $sys(literal)} {
	    if {($cmd in {name varName}) && ([lindex $etl 1] ni $locals)} {
		# non-local variable values are fixed when function is defined
		return [list  uval  {*}[namespace eval eval [tclize $etl]]]
	    } else {
		return $etl
	    }
	} else {
	    # recurse down expresstion tree list
	    # does not evaluate constant subexpressions, yet
	    return [concat $cmd \
			[lmap x [lrange $etl 1 end] {
			    resolveNonLocals $callingLevel $lvl $locals $x
			}]]
	}
    }
		

    proc eval::, {a b} {
	# append the new item to the list







|



|













|







1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
				    [lmap x [lrange {%s} [llength $args] end] {
					::uexpr::ltx::name $x
				    }]]
	} $name $paramNames]
    }


    proc resolveNonLocals {callingLevel locals etl} {
	# recurse through the expression tree list (etl)
	# resolving all nonlocal variables to uvals
	# return a modified copy of the etl
	variable sys
	set cmd [lindex $etl 0]
	if {$cmd in $sys(literal)} {
	    if {($cmd in {name varName}) && ([lindex $etl 1] ni $locals)} {
		# non-local variable values are fixed when function is defined
		return [list  uval  {*}[namespace eval eval [tclize $etl]]]
	    } else {
		return $etl
	    }
	} else {
	    # recurse down expresstion tree list
	    # does not evaluate constant subexpressions, yet
	    return [concat $cmd \
			[lmap x [lrange $etl 1 end] {
			    resolveNonLocals $callingLevel $locals $x
			}]]
	}
    }
		

    proc eval::, {a b} {
	# append the new item to the list
1409
1410
1411
1412
1413
1414
1415





1416
1417
1418
1419
1420
1421
1422
	}

	    
	proc log10 {x} {
	    return "\\log_{10} \\left ( $x \\right ) "
	}







	# ltx::func ensemble generates LaTeX for function calls
	namespace export *
	namespace ensemble create -command ::uexpr::ltx::func  \
	    -unknown ::uexpr::unknownLtxFunc  \
	    -prefixes 0
    }







>
>
>
>
>







1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
	}

	    
	proc log10 {x} {
	    return "\\log_{10} \\left ( $x \\right ) "
	}


	proc sum {var start end expr} {
	    return "\\sum_{$var=$start}^{$end} [namespace eval ::uexpr::ltx $expr]"
	}


	# ltx::func ensemble generates LaTeX for function calls
	namespace export *
	namespace ensemble create -command ::uexpr::ltx::func  \
	    -unknown ::uexpr::unknownLtxFunc  \
	    -prefixes 0
    }
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
	return $opVals
    }


    # named functions
    # must be defined in ufunc namespace
    # all parameters follow the function name on the command line
    # return a uvalue
    # unknown functions cause an error

    namespace eval ufunc {
	namespace path [namespace parent]

	# generate an error message for an unknown function
	# this would be the place to handle user defined functions too







|







1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
	return $opVals
    }


    # named functions
    # must be defined in ufunc namespace
    # all parameters follow the function name on the command line
    # return a uval
    # unknown functions cause an error

    namespace eval ufunc {
	namespace path [namespace parent]

	# generate an error message for an unknown function
	# this would be the place to handle user defined functions too
1693
1694
1695
1696
1697
1698
1699










1700
1701
1702
1703
1704
1705
1706
	    radians [::tcl::mathfunc::asin [onlyUnitless $x]]
	}
	
	proc atan {x} {
	    radians [::tcl::mathfunc::atan [onlyUnitless $x]]
	}













	# the absolute value function makes sense with any units
	# complains if there is not 1 parameter
	
	proc abs {x} {
	    lreplace $x 0 0  [::tcl::mathfunc::abs [lindex $x 0]]







>
>
>
>
>
>
>
>
>
>







1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
	    radians [::tcl::mathfunc::asin [onlyUnitless $x]]
	}
	
	proc atan {x} {
	    radians [::tcl::mathfunc::atan [onlyUnitless $x]]
	}

	proc atan2 {a b} {
	# check that dimensions are the same
	set q $a
	foreach ua [lassign $q a] ub [lassign $b b] {
	    if {0.001 < [expr abs($ua - $ub)]} {
		error "atan2(y,x): trying to take arctangent of y/x for values with different units"
	    }
	}
	radians [expr {atan2($a,$b)}]
    }


	# the absolute value function makes sense with any units
	# complains if there is not 1 parameter
	
	proc abs {x} {
	    lreplace $x 0 0  [::tcl::mathfunc::abs [lindex $x 0]]
1744
1745
1746
1747
1748
1749
1750
1751



















1752
1753

1754
1755
1756
1757
1758
1759
1760

	proc min {args} {lreplace [lindex $args 0] 0 0 \
			     [::tcl::mathfunc::min {*}[func2+u $args]]}

	proc max {args} {lreplace [lindex $args 0] 0 0 \
			     [::tcl::mathfunc::max {*}[func2+u $args]]}





















	# return a unitless random real number between 0.0 and 1.0


	proc rand {} {
	    # append the units from a unitless "unit" value
	    lreplace $::uexpr::units(zero) 0 0 [expr {rand()}]
	}











>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|

>







1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923

	proc min {args} {lreplace [lindex $args 0] 0 0 \
			     [::tcl::mathfunc::min {*}[func2+u $args]]}

	proc max {args} {lreplace [lindex $args 0] 0 0 \
			     [::tcl::mathfunc::max {*}[func2+u $args]]}


	proc sum {var start end expr} {
	    # sum expression for a range of indexes
	    # get context for variables 3 levels up from here
	    set callingLevel [uplevel 3 set callingLevel]
	    if {[HasUnits $start]} {
		error {sum initial index value must be unitless}
	    }
	    set i [expr {round([lindex $start 0])}]
	    if {[HasUnits $end]} {
		error {sum final index value must be unitless}
	    }
	    set last [expr {round([lindex $end 0])}]
	    set sum $uexpr::units(zero)
	    for {} {$i <= $last} {incr i} {
		upset $callingLevel $var $i
		set sum [::uexpr::eval::+ $sum [namespace eval ::uexpr::eval $expr]]
	    }
	    return $sum
	}
	

	# return a unitless random real number between 0.0 and 1.0
	proc rand {} {
	    # append the units from a unitless "unit" value
	    lreplace $::uexpr::units(zero) 0 0 [expr {rand()}]
	}



1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798

1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
    # in uExpr context)
    # then variable in the calling context
    #  variables are looked up first as simple names: aa.bb is $aa.bb
    #  then as array elements: aa.bb is $aa(bb), and aa is $aa() 
    #  finally, if a variable is not found, as a unit with the given name
    # In any case, the value is an expression which is parsed and evaluated
    # to return a uval
    # !!! NEEDS REVIEW!!!
    #   add logic to search unit names before variables, or only units,
    # or only variables ???
    #   could use $ prefix to search variables first, ":" search units first
    # 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,
	# one level for this proc, one for namespace eval
	upvar 2 callingLevel callingLevel  locals locals  lvl lvl

	dm {in name $name  callingLevel=$callingLevel}
	
	# generate possible array variable and member names
	# member name is everything after the first "."
	set mname [join [lassign [split $name "."] aname] "."]
	# if its a variable name in the calling scope
	if {[info exists locals] && [dict exists $locals $name]} {
	    return [dict get $locals $name]
	} elseif {![catch [list upset $callingLevel $name] value]} {
	    # convert to a unit list value
	    dm {variable name=$name val=$value}
	    return [namespace eval ::uexpr::eval [tclize [parse $value]]]
	    # if its an array ref, aaa.bb means aaa(bb), and aaa means aaa()
	} elseif {![catch [list upset $callingLevel ${aname}($mname)] value]} {
	    return [namespace eval ::uexpr::eval [tclize [parse $value]]]
	    # may have problems in calc.tcl if aaa is defined before aaa.bb
	    dm {variable array=$aname  member=$mname val=$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  lvl lvl
	dm {in name $name  callingLevel=$callingLevel}
	
	# generate possible array variable and member names
	# member name is everything after the first "."
	set mname [join [lassign [split $name "."] aname] "."]
	# if its a variable name in the calling scope
	if {[dict exists $locals $name]} {
	    return [dict get $locals $name]
	} elseif {![catch [list upset $callingLevel $name] value]} {
	    # convert to a unit list value
	    dm {variable name=$name val=$value}
	    return [namespace eval ::uexpr::eval [tclize [parse $value]]]
	    # if its an array ref, aaa.bb means aaa(bb), and aaa means aaa()
	} elseif {![catch [list upset $callingLevel ${aname}($mname)] value]} {
	    return [namespace eval ::uexpr::eval [tclize [parse $value]]]
	    # may have problems in calc.tcl if aaa is defined before aaa.bb
	    dm {variable array=$aname  member=$mname val=$value}
	} else {
	    error "uExpr: $name is not a variable name"
	}
    }








<
<
|
<
|

|



|

|
>











|


|


















|











|


|







1942
1943
1944
1945
1946
1947
1948


1949

1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
    # in uExpr context)
    # then variable in the calling context
    #  variables are looked up first as simple names: aa.bb is $aa.bb
    #  then as array elements: aa.bb is $aa(bb), and aa is $aa() 
    #  finally, if a variable is not found, as 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 (":")

    # 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}
	
	# generate possible array variable and member names
	# member name is everything after the first "."
	set mname [join [lassign [split $name "."] aname] "."]
	# if its a variable name in the calling scope
	if {[info exists locals] && [dict exists $locals $name]} {
	    return [dict get $locals $name]
	} 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 an array ref, aaa.bb means aaa(bb), and aaa means aaa()
	} elseif {![catch [list upset $callingLevel ${aname}($mname)] value]} {
	    return [namespace eval ::uexpr::eval [tclizeExpr $value]]
	    # may have problems in calc.tcl if aaa is defined before aaa.bb
	    dm {variable array=$aname  member=$mname val=$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}
	
	# generate possible array variable and member names
	# member name is everything after the first "."
	set mname [join [lassign [split $name "."] aname] "."]
	# if its a variable name in the calling scope
	if {[dict exists $locals $name]} {
	    return [dict get $locals $name]
	} 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 an array ref, aaa.bb means aaa(bb), and aaa means aaa()
	} elseif {![catch [list upset $callingLevel ${aname}($mname)] value]} {
	    return [namespace eval ::uexpr::eval [tclizeExpr $value] ]
	    # may have problems in calc.tcl if aaa is defined before aaa.bb
	    dm {variable array=$aname  member=$mname val=$value}
	} else {
	    error "uExpr: $name is not a variable name"
	}
    }

1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
                   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 [tclize [parse $val]]
    }
    


    proc eval::setArrayAccess {name indexList value} {
	# save value in nested list in named variable
	dm {arrayAccess $name level: [info level]}







|







2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
                   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]}
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374

2375
2376
2377
2378
2379
2380
2381
2382
2383
2384







2385

2386
2387


2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404























2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
	lreplace $q 0 0 [expr {$a + $b}]
    }


    interp alias  {} ltx::+  {} ::uexpr::ltx::BinaryOp +


    # not "perfect", error message is about add, but can be subtract in this case
    interp alias {} eval::+CR  {} ::uexpr::eval::+


    # LaTeX for a line break in an equation at an inline binary operator
    proc ltx::+CR {a b} {
	# look for \U229F leading the right operand to indicate line break
	# at subtract
	# fix for -- precedence problem (1 + 2 -- 3 + 4 = 4 not -4)
	set bb [string trimleft $b]
	if {"\U229F" eq [string index $bb 0]} {
	    # line break at subtract
	    string cat \
		{ \begin{array}{l} }  \
		$a  \
		{ - \\ \mbox{} - }  \
		[string range $b 1 end]  \
		{ \end{array} }
	} else {
	    # line break at add
	    string cat \
		{ \begin{array}{l} }  \
		$a  \
		{ + \\ \mbox{} + }  \
		$b  \
		{ \end{array} }
	}

    }
    

    # evaluate -CR, which acts like a negate
    # but is always right operand of a +CR
    interp alias {} eval::-CR {} ::uexpr::eval::neg

    proc ltx::-CR {u} {
	# format similar to negate, but use boxed -
	# which is removed later when the associated +CR







	# formats the line break in the equation

	string cat \U229F $u
    }





    proc eval::- {a b} {
	# check that dimensions are the same
	set q $a
	foreach ua [lassign $q a] ub [lassign $b b] {
	    if {0.001 < [expr abs($ua - $ub)]} {
		error "trying to subtract values with different units"
	    }
	}
	lreplace $q 0 0 [expr {$a - $b}]
    }

    interp alias  {} ltx::- {} ::uexpr::ltx::BinaryOp -


























    proc eval::\U2248 {a b} {
	# check that values are equal
	# check that dimensions are the same
	foreach ua [lassign $a aa] ub [lassign $b bb] {
	    if {0.001 < [expr abs($ua - $ub)]} {
		error "\U2248: trying to compare values with different units"
	    }
	}
	lreplace $::uexpr::units(one) 0 0 \
	    [expr {[lindex $aa 0] == [lindex $bb 0]}]
    }

    interp alias {} ltx::\U2248 {} ::uexpr::ltx::BinaryOp =

    # this one is for = appearing within expressions
    # (see calc.tcl, can occur within eqn)
    # normally any "=" is handled by an outer proc level
    # (ie in uexpr or calc ...)
    interp alias {} ltx::= {} uexpr::ltx::BinaryOp =


    proc eval::\U2249 {a b} {
	# check that values are not equal
	# check that dimensions are the same
	foreach ua [lrange $a 1 end] ub [lrange $b 1 end] {
	    if {0.001 < [expr abs($ua - $ub)]} {
		error "\U2249: trying to compare values with different units"
	    }
	}
	lreplace $::uexpr::units(one) 0 0 \
	    [expr {[lindex $a 0] != [lindex $b 0]}]
    }

    interp alias  {} ltx::\U2249 {} ::uexpr::ltx::BinaryOp "\\neq "




    proc eval::< {a b} {
	dm ult
	# check that dimensions are the same
	# build empty units
	set c [list 0]
	foreach ua [lrange $a 1 end] ub [lrange $b 1 end] {
	    if {0.001 < [expr abs($ua - $ub)]} {
		error "<: trying to compare values with different units"
	    }
	    lappend c 0 
	}
	lreplace $c 0 0 [expr {[lindex $a 0] < [lindex $b 0]}]
    }

    interp alias  {} ltx::< {} ::uexpr::ltx::BinaryOp " < "



    proc eval::\U2264 {a b} {
	# check that dimensions are the same
	# build empty units
	set c [list 0]
	foreach ua [lrange $a 1 end] ub [lrange $b 1 end] {
	    if {0.001 < [expr abs($ua - $ub)]} {
		error "ule: trying to compare values with different units"
	    }
	    lappend c 0 
	}
	lreplace $c 0 0 [expr {[lindex $a 0] <= [lindex $b 0]}]
    }


    interp alias  {} ltx::\U2264  {} ::uexpr::ltx::BinaryOp " \\leq "



    proc eval::\U2265 {a b} {
	# check that dimensions are the same
	# build empty units
	set c [list 0]
	foreach ua [lrange $a 1 end] ub [lrange $b 1 end] {
	    if {0.001 < [expr abs($ua - $ub)]} {
		error "uge: trying to compare values with different units"
	    }
	    lappend c 0 
	}
	lreplace $c 0 0 [expr {[lindex $a 0] >= [lindex $b 0]}]
    }

    interp alias  {} ltx::\U2265  {} ::uexpr::ltx::BinaryOp " \\geq "



    proc eval::> {a b} {
	# check that dimensions are the same
	set c [list 0]
	foreach ua [lrange $a 1 end] ub [lrange $b 1 end] {
	    if {0.001 < [expr abs($ua - $ub)]} {
		error "ugt: trying to compare values with different units"
	    }
	    lappend c 0
	}
	lreplace $c 0 0 [expr {[lindex $a 0] > [lindex $b 0]}]
    }


    interp alias  {} ltx::>  {} ::uexpr::ltx::BinaryOp " > "



    proc eval::* {a b} {
	set val [expr [lindex $a 0] * [lindex $b 0]]
	foreach x [lrange $a 1 end] y [lrange $b 1 end] {
	    lappend val [expr $x + $y]







<
|
|
|
<
<
<
|
<
<
<
<
<
<
|
<
<
<
<
<
<
<
|
<
<
<

>

|

<
<
<
|
|
<
<
>
>
>
>
>
>
>
|
>
|
|
>
>
|
|















>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>













|




















|


















|

















|
















|
















|







2502
2503
2504
2505
2506
2507
2508

2509
2510
2511



2512






2513







2514



2515
2516
2517
2518
2519



2520
2521


2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
	lreplace $q 0 0 [expr {$a + $b}]
    }


    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 {







	    set a [$op $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}{l} }  $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} }
    }
    

    proc eval::- {a b} {
	# check that dimensions are the same
	set q $a
	foreach ua [lassign $q a] ub [lassign $b b] {
	    if {0.001 < [expr abs($ua - $ub)]} {
		error "trying to subtract values with different units"
	    }
	}
	lreplace $q 0 0 [expr {$a - $b}]
    }

    interp alias  {} ltx::- {} ::uexpr::ltx::BinaryOp -


    proc eval::CC {a args} {
	# evaluate chained compares
	# right op becomes left op for next compare
	# return true only if all compares are true
	foreach {op b} $args {
	    if {0 == [lindex [$op $a $b] 0]} {
		return $::uexpr::units(zero)
	    }
	    set a $b
	}
	return $::uexpr::units(one)
    }

    
    proc ltx::CC {a args} {
	# write LaTeX for chained compares
	variable ltxSym
	foreach {op b} $args {
	    # continue by appending operator symbol followed by next subexpression
	    append a " "  $ltxSym($op) " "  $b
	}
	return $a
    }

    proc eval::\U2248 {a b} {
	# check that values are equal
	# check that dimensions are the same
	foreach ua [lassign $a aa] ub [lassign $b bb] {
	    if {0.001 < [expr abs($ua - $ub)]} {
		error "\U2248: trying to compare values with different units"
	    }
	}
	lreplace $::uexpr::units(one) 0 0 \
	    [expr {[lindex $aa 0] == [lindex $bb 0]}]
    }

    #interp alias {} ltx::\U2248 {} ::uexpr::ltx::BinaryOp =

    # this one is for = appearing within expressions
    # (see calc.tcl, can occur within eqn)
    # normally any "=" is handled by an outer proc level
    # (ie in uexpr or calc ...)
    interp alias {} ltx::= {} uexpr::ltx::BinaryOp =


    proc eval::\U2249 {a b} {
	# check that values are not equal
	# check that dimensions are the same
	foreach ua [lrange $a 1 end] ub [lrange $b 1 end] {
	    if {0.001 < [expr abs($ua - $ub)]} {
		error "\U2249: trying to compare values with different units"
	    }
	}
	lreplace $::uexpr::units(one) 0 0 \
	    [expr {[lindex $a 0] != [lindex $b 0]}]
    }

    #interp alias  {} ltx::\U2249 {} ::uexpr::ltx::BinaryOp "\\neq "




    proc eval::< {a b} {
	dm ult
	# check that dimensions are the same
	# build empty units
	set c [list 0]
	foreach ua [lrange $a 1 end] ub [lrange $b 1 end] {
	    if {0.001 < [expr abs($ua - $ub)]} {
		error "<: trying to compare values with different units"
	    }
	    lappend c 0 
	}
	lreplace $c 0 0 [expr {[lindex $a 0] < [lindex $b 0]}]
    }

    #interp alias  {} ltx::< {} ::uexpr::ltx::BinaryOp " < "



    proc eval::\U2264 {a b} {
	# check that dimensions are the same
	# build empty units
	set c [list 0]
	foreach ua [lrange $a 1 end] ub [lrange $b 1 end] {
	    if {0.001 < [expr abs($ua - $ub)]} {
		error "ule: trying to compare values with different units"
	    }
	    lappend c 0 
	}
	lreplace $c 0 0 [expr {[lindex $a 0] <= [lindex $b 0]}]
    }


    #interp alias  {} ltx::\U2264  {} ::uexpr::ltx::BinaryOp " \\leq "



    proc eval::\U2265 {a b} {
	# check that dimensions are the same
	# build empty units
	set c [list 0]
	foreach ua [lrange $a 1 end] ub [lrange $b 1 end] {
	    if {0.001 < [expr abs($ua - $ub)]} {
		error "uge: trying to compare values with different units"
	    }
	    lappend c 0 
	}
	lreplace $c 0 0 [expr {[lindex $a 0] >= [lindex $b 0]}]
    }

    #interp alias  {} ltx::\U2265  {} ::uexpr::ltx::BinaryOp " \\geq "



    proc eval::> {a b} {
	# check that dimensions are the same
	set c [list 0]
	foreach ua [lrange $a 1 end] ub [lrange $b 1 end] {
	    if {0.001 < [expr abs($ua - $ub)]} {
		error "ugt: trying to compare values with different units"
	    }
	    lappend c 0
	}
	lreplace $c 0 0 [expr {[lindex $a 0] > [lindex $b 0]}]
    }


    #interp alias  {} ltx::>  {} ::uexpr::ltx::BinaryOp " > "



    proc eval::* {a b} {
	set val [expr [lindex $a 0] * [lindex $b 0]]
	foreach x [lrange $a 1 end] y [lrange $b 1 end] {
	    lappend val [expr $x + $y]
2575
2576
2577
2578
2579
2580
2581

2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598

2599




2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614

2615
2616
2617

2618
2619
2620

2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631



2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
    }




    # convert an expression nested list to a tcl command
    # Each level of nesting becomes a command substitution [...]

    proc tclize {nl} {
	variable sys
	# if the nested list has only literal operands
	# (it is not actually a nested list)
	if {[lindex $nl 0] in $sys(literal)} {
	    return $nl
	} else {
	    # process operands as nested lists
	    foreach op [lassign $nl cmd] {
		append cmd { [} [tclize $op] {]}
	    }
	    return $cmd
	}
    }


    proc tclizeExpr {expr} {

	# memoize expressions in uexpr::exprs dict




	variable exprs
	# disable memoizing if debug is set
	variable debug
	if {!$debug && [dict exists $exprs $expr]} {
	    dict get $exprs $expr
	} else {
	    set cmd [tclize [parse $expr]]
	    dict set exprs $expr $cmd
	    return $cmd
	}
    }


    # uExpr
    # Basic expression evaluator

    # The callingLevel points to the stack frame where named variables
    #   in the expression being evaluated might be defined
    # The expression is a string

    # locals is a dict of variable names and uvals
    #  searched first when looking up variables,
    #  useful with user defined functions

    # Returns a uval
    # This proc exists to provide a stack frame
    # containing a common context for parsing and evaluating expressions.
    # You probably want to use "uexpr" instead

    proc uExpr {callingLevel str {locals {}}} {
	# variables must be defined here (for access by uplevel??)
	variable units
	variable sys
	# ?? is lvl really useful ??
	set lvl #[info level]



	namespace eval eval [tclizeExpr $str]
    }


    proc uProc {callingLevel exprCmd {locals {}}} {
	# like uExpr but expects a preparsed and tclized expression string
	# in exprCmd
	# used by user defined func
	variable units
	variable sys
	set lvl #[info level]
	namespace eval eval $exprCmd
    }



    proc uSet {callingLevel str {locals {}}} {
	# save a value to a variable as a value, list element
	# or possibly Tcl array element
	# variables must be defined here (for access by uplevel??)
	variable units
	variable sys
	set lvl #[info level]
	namespace eval set [tclizeExpr $str]
    }


    # evaluate a tclized expression

    proc eval {callingLevel cmd {locals {}}} {
	variable units
	variable sys
	set lvl #[info level]
	namespace eval eval $cmd  
    }


    # generate LaTeX for the expression
    proc ltx {expr} {
	namespace eval ltx [tclizeExpr $expr]
    }

    # for convienence
    # proc = {args} {uplevel 1 uexpr $args}



    # now some routines for units



    # convert a uvalue to a valid expression using base units
    # This is more readable than a uvalue list

    proc baseUnitStr {bu} {
	# return expression for given list of base unit exponents
	# ub is a uval with magnitude removed
	# if there are no units in numerator (exponent> 0) first cgar is "/"
	variable uBaseNames

	dm {baseUnitStr [llength $bu] bu=$bu}
	if {[llength $bu] > [llength $uBaseNames]} {
	    error "too many unit exponents in $bu"
	}
	set str ""







>

















>

>
>
>
>















>


<
>

|
|
>


|


|

|
|

|
>
>
>








|
|
|









|
|
|







|
|
|









<
<
<






|
|



|
|







2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793

2794
2795
2796
2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849
2850
2851
2852
2853
2854



2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867
2868
2869
2870
2871
2872
2873
2874
    }




    # convert an expression nested list to a tcl command
    # Each level of nesting becomes a command substitution [...]
    # does not memoize (no expression string available here)
    proc tclize {nl} {
	variable sys
	# if the nested list has only literal operands
	# (it is not actually a nested list)
	if {[lindex $nl 0] in $sys(literal)} {
	    return $nl
	} else {
	    # process operands as nested lists
	    foreach op [lassign $nl cmd] {
		append cmd { [} [tclize $op] {]}
	    }
	    return $cmd
	}
    }


    proc tclizeExpr {expr} {
	# 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
	# disable memoizing if debug is set
	variable debug
	if {!$debug && [dict exists $exprs $expr]} {
	    dict get $exprs $expr
	} else {
	    set cmd [tclize [parse $expr]]
	    dict set exprs $expr $cmd
	    return $cmd
	}
    }


    # uExpr
    # Basic expression evaluator
    # The expression is a string
    # The callingLevel points to the stack frame where named variables
    #   in the expression being evaluated might be defined

    #   default is 2 levels below current level
    # locals is a dict of variable names and uvals
    #   searched first when looking up variables,
    #   useful with user defined functions
    #   defaults to empty
    # Returns a uval
    # This proc exists to provide a stack frame
    # containing a common context for  evaluating expressions.
    # You probably want to use "uexpr" instead

    proc uExpr {str {callingLevel ""} {locals {}}} {
	# variables must be defined here (for access by uplevel??)
	#variable units
	#variable sys
	# ?? is lvl really useful ??
	# set lvl #[info level]
	if {"" eq $callingLevel} {
	    set callingLevel [expr {[info level] - 2}]
	}
	namespace eval eval [tclizeExpr $str]
    }


    proc uProc {callingLevel exprCmd {locals {}}} {
	# like uExpr but expects a preparsed and tclized expression string
	# in exprCmd
	# used by user defined func
	#variable units
	#variable sys
	#set lvl #[info level]
	namespace eval eval $exprCmd
    }



    proc uSet {callingLevel str {locals {}}} {
	# save a value to a variable as a value, list element
	# or possibly Tcl array element
	# variables must be defined here (for access by uplevel??)
	#variable units
	#variable sys
	#set lvl #[info level]
	namespace eval set [tclizeExpr $str]
    }


    # evaluate a tclized expression

    proc eval {callingLevel cmd {locals {}}} {
	#variable units
	#variable sys
	#set lvl #[info level]
	namespace eval eval $cmd  
    }


    # generate LaTeX for the expression
    proc ltx {expr} {
	namespace eval ltx [tclizeExpr $expr]
    }






    # now some routines for units



    # convert a uval to a valid expression using base units (uvalue)
    # This is more readable than a uval list

    proc baseUnitStr {bu} {
	# return expression for given list of base unit exponents
	# bu is a uval with magnitude removed
	# if there are no units in numerator (exponent> 0) first char is "/"
	variable uBaseNames

	dm {baseUnitStr [llength $bu] bu=$bu}
	if {[llength $bu] > [llength $uBaseNames]} {
	    error "too many unit exponents in $bu"
	}
	set str ""
2722
2723
2724
2725
2726
2727
2728
2729

2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742


2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
	}
	return $str
    }


    proc baseUnitExpStr {bu} {
	# return expression for given list of base unit exponents
	# as product of base unit powers

	# bu is a uval with magnitude removed
	variable uBaseNames
	
	dm "baseUnitStr [llength $bu] bu=$bu"
	if {[llength $bu] > [llength $uBaseNames]} {
	    error "too many unit exponents in $bu"
	}
	set str ""
	# append any units with non-zero exponents
	foreach  name $uBaseNames  x $bu  {
	    if {"" eq $x} {break}
	    if {0.01 > abs($x)} {continue}
	    dm "uStr $name $x"


	    if {1 != $x} {
		append str [format { %s^%g} $name $x]
	    } else {
		append str [format { %s} $name]
	    }
	}
	return $str
    }


    proc ltxBaseUnitExpStr {bu} {
	# return LaTeX for given list of base unit exponents
	# as product of powers of base units
	# bu is a uval with magnitude removed
	variable uBaseNames
	
	dm "baseUnitStr [llength $bu] bu=$bu"
	if {[llength $bu] > [llength $uBaseNames]} {
	    error "too many unit exponents in $bu"
	}
	set str ""
	# append any units with non-zero exponents
	foreach  name $uBaseNames  x $bu  {
	    if {"" eq $x} {break}
	    if {0.01 > abs($x)} {continue}
	    dm "uStr $name $x"
	    append str {\cdot \mathrm} \{ $name \}
	    if {1 != $x} {
		append str [format {^%g} $x]
	    }
	}
	return $str
    }


    proc uStr {u} {
	# generate an expression using only base units for the given uvaue list
	dm "uStr [llength $u] u=$u"
	append str [baseUnitStr [lassign $u str]]
    }


    proc unitizeValue {callingLevel uval reqUnits {fmt %s}} {
	dm "unitizeValue val: $uval  reqUnits: $reqUnits"
	# adjust result string value for requested units given in expr
	# this code is common to uexpr and ltxExpr
	if {"" ne $reqUnits} {
	    set uval [eval::/ $uval [uExpr $callingLevel $reqUnits]]
	}
	lset uval 0 [format $fmt [lindex $uval 0]]
	set opt [uStr $uval]
	# if specific units are requested
	if {"" ne $reqUnits} {
	    # trim off leading factor of 1
	    regexp {^1(?:\.0*)? *([ /*].*)} $reqUnits x reqUnits
	    # if value has units, and requested units are not empty
	    # and do not start with * or /
	    # insert a *
	    if {[HasUnits $uval]
		&& "" ne $reqUnits
		&& ![regexp {^[*/]} $reqUnits]} {
		append opt  " * "  $reqUnits
	    } else {
		append opt " " $reqUnits
	    }	    
	}
	dm "unitizeResult output: $opt"
	return $opt







|
>



|









>
>

|

|












|











|


















|












|







2900
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913
2914
2915
2916
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
2930
2931
2932
2933
2934
2935
2936
2937
2938
2939
2940
2941
2942
2943
2944
2945
2946
2947
2948
2949
2950
2951
2952
2953
2954
2955
2956
2957
2958
2959
2960
2961
2962
2963
2964
2965
2966
2967
2968
2969
2970
2971
2972
2973
2974
2975
2976
2977
2978
2979
2980
2981
2982
2983
2984
2985
2986
2987
2988
2989
2990
2991
	}
	return $str
    }


    proc baseUnitExpStr {bu} {
	# return expression for given list of base unit exponents
	# as product or quotient of base unit powers
	# There are no spaces in the unit expr, and it starts with * or /
	# bu is a uval with magnitude removed
	variable uBaseNames
	
	dm "baseUnitExpStr [llength $bu] bu=$bu"
	if {[llength $bu] > [llength $uBaseNames]} {
	    error "too many unit exponents in $bu"
	}
	set str ""
	# append any units with non-zero exponents
	foreach  name $uBaseNames  x $bu  {
	    if {"" eq $x} {break}
	    if {0.01 > abs($x)} {continue}
	    dm "uStr $name $x"
	    append str [expr {($x>0)? "*" : "/"}]
	    set x [::tcl::mathfunc::abs $x]
	    if {1 != $x} {
		append str [format {%s^%g} $name $x]
	    } else {
		append str [format {%s} $name]
	    }
	}
	return $str
    }


    proc ltxBaseUnitExpStr {bu} {
	# return LaTeX for given list of base unit exponents
	# as product of powers of base units
	# bu is a uval with magnitude removed
	variable uBaseNames
	
	dm "ltxBaseUnitExpStr [llength $bu] bu=$bu"
	if {[llength $bu] > [llength $uBaseNames]} {
	    error "too many unit exponents in $bu"
	}
	set str ""
	# append any units with non-zero exponents
	foreach  name $uBaseNames  x $bu  {
	    if {"" eq $x} {break}
	    if {0.01 > abs($x)} {continue}
	    dm "uStr $name $x"
	    append str {\cdot \mathrm} \{ $name \}
	    if {1 != $x} {
		append str [format {^{%g}} $x]
	    }
	}
	return $str
    }


    proc uStr {u} {
	# generate an expression using only base units for the given uvaue list
	dm "uStr [llength $u] u=$u"
	append str [baseUnitStr [lassign $u str]]
    }


    proc unitizeValue {callingLevel uval reqUnits {fmt %s}} {
	dm "unitizeValue val: $uval  reqUnits: $reqUnits"
	# adjust result string value for requested units given in expr
	# this code is common to uexpr and ltxExpr
	if {"" ne $reqUnits} {
	    set uval [eval::/ $uval [namespace eval eval [tclizeExpr $reqUnits]]]
	}
	lset uval 0 [format $fmt [lindex $uval 0]]
	set opt [uStr $uval]
	# if specific units are requested
	if {"" ne $reqUnits} {
	    # trim off leading factor of 1
	    regexp {^1(?:\.0*)? *([ /*].*)} $reqUnits x reqUnits
	    # if value has units, and requested units are not empty
	    # and do not start with * or /
	    # insert a *
	    if {[HasUnits $uval]
		&& "" ne $reqUnits
		&& ![string match {[*/]*} $reqUnits]} {
		append opt  " * "  $reqUnits
	    } else {
		append opt " " $reqUnits
	    }	    
	}
	dm "unitizeResult output: $opt"
	return $opt
2854
2855
2856
2857
2858
2859
2860
2861
2862

2863
2864
2865
2866
2867
2868
2869
2870
2871
2872
2873
2874
2875
2876
2877
2878
2879
2880
2881
2882
2883
2884
2885
2886
2887
2888
2889
2890
2891
2892
2893
2894
2895
2896
2897
2898
2899
2900
2901
2902
2903
2904
2905
2906
    # returns a value that is both readable and usable in further calculations
    # Includes special processing for "=",
    #   saves right side and appends to result

    proc uexpr {args} {
	# evaluate an expression
	# return result as valid unit expression
	set lvl #[set cl [info level]]
	set callingLevel #[incr cl -1]

	# segment expression at = (skipping == != <= ...) 
	set runits [lassign [segmentExpr [join $args]] expr]
	# evaluate expr in first segment
	# use only the last set of requested units, if it exists
	unitizeResult \
	    $callingLevel  \
	    [uExpr $callingLevel $expr]  \
	    [lindex $runits end]
    }



    # similar to uexpr, but saves result in a variable
    # The variable name is formatted the same as variable references in uexpr
    # The variable can be a reference to a tcl array: a(b) or maybe a.d
    # 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 in a form that is both readable
    # and usable in further calculations
    # Includes special processing for "=",
    #   saves right side and appends to result

    proc uset {varExpr args} {
	# evaluate an expression
	# return result as valid unit expression
	set lvl #[set cl [info level]]
	set callingLevel #[incr cl -1]
	# there are no local variables
	set locals {}
	# evaluate expression in args
	# segment expression at = (skipping == != <= ...) 
	set runits [lassign [segmentExpr [join $args]] expr]
	# evaluate expr in first segment
	# use only the last set of requested units, if it exists
	set val [unitizeResult \
		     $callingLevel  \
		     [uExpr $callingLevel $expr]  \
		     [lindex $runits end]]
	# parse variable reference
	set ve [parse $varExpr]
	#puts "ve: $ve"
	# make primary variable reference a set referenced value
	switch [lindex $ve 0] {
	    name {







<
|
>






|


















<
|









|







3035
3036
3037
3038
3039
3040
3041

3042
3043
3044
3045
3046
3047
3048
3049
3050
3051
3052
3053
3054
3055
3056
3057
3058
3059
3060
3061
3062
3063
3064
3065
3066
3067
3068

3069
3070
3071
3072
3073
3074
3075
3076
3077
3078
3079
3080
3081
3082
3083
3084
3085
3086
    # returns a value that is both readable and usable in further calculations
    # Includes special processing for "=",
    #   saves right side and appends to result

    proc uexpr {args} {
	# evaluate an expression
	# return result as valid unit expression

	set callingLevel #[expr {[info level] -1}]
	set locals {}
	# segment expression at = (skipping == != <= ...) 
	set runits [lassign [segmentExpr [join $args]] expr]
	# evaluate expr in first segment
	# use only the last set of requested units, if it exists
	unitizeResult \
	    $callingLevel  \
	    [namespace eval eval [tclizeExpr $expr]]  \
	    [lindex $runits end]
    }



    # similar to uexpr, but saves result in a variable
    # The variable name is formatted the same as variable references in uexpr
    # The variable can be a reference to a tcl array: a(b) or maybe a.d
    # 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 in a form that is both readable
    # and usable in further calculations
    # Includes special processing for "=",
    #   saves right side and appends to result

    proc uset {varExpr args} {
	# evaluate an expression
	# return result as valid unit expression

	set callingLevel #[expr {[info level] -1}]
	# there are no local variables
	set locals {}
	# evaluate expression in args
	# segment expression at = (skipping == != <= ...) 
	set runits [lassign [segmentExpr [join $args]] expr]
	# evaluate expr in first segment
	# use only the last set of requested units, if it exists
	set val [unitizeResult \
		     $callingLevel  \
		     [namespace eval eval [tclizeExpr $expr]]  \
		     [lindex $runits end]]
	# parse variable reference
	set ve [parse $varExpr]
	#puts "ve: $ve"
	# make primary variable reference a set referenced value
	switch [lindex $ve 0] {
	    name {
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
2930
2931
2932
2933
2934
2935
2936
2937
2938
2939
2940
2941
2942
2943
2944
2945
2946
2947
2948
2949
2950
2951
2952
2953
2954
2955
2956
2957
2958
    }



    # format expression and result with LaTeX
    # return a list of:
    #   LaTeX formatted expression
    #   result of calculation (as an expression strinf)
    #   result magnitude
    #     (this number may need formatting for significant digets)
    #   LaTeX formatted result units expression (may be empty string)
    #   repeat last two items for each additional set of requested units

    proc ltxExpr {str {locals ""}} {
	# evaluate an expression
	# return result as valid unit expression
	set lvl #[set cl [info level]]
	set callingLevel #[incr cl -1]
	# segment expression at = (skipping == != <= ...)
	set segs [segmentExpr $str]
	set rucmds [lassign  \
			[lmap seg $segs {
			    if {"" ne $seg} {tclizeExpr $seg} else {list}
			}]  \
			ecmd]
	# evaluate first segment
	set val [namespace eval eval $ecmd]
	set opt [list [namespace eval ltx $ecmd]]
	# save result of expression
	lappend opt [unitizeResult $callingLevel $val [lindex $segs 1]]
	# if units are requested
	if {0 == [llength $rucmds]} {
	    set rucmds [list ""]
	}
	#puts "rucmds  $rucmds"
	foreach runit $rucmds {
	    # if there are requested units
	    if {"" ne $runit} {
		# divide result by requested units
		set rval [eval::/ $val [namespace eval eval  $runit]]
		# get requested units string
		set runit [namespace eval ltx $runit]







|








<
|












|
|
<
<
<







3097
3098
3099
3100
3101
3102
3103
3104
3105
3106
3107
3108
3109
3110
3111
3112

3113
3114
3115
3116
3117
3118
3119
3120
3121
3122
3123
3124
3125
3126
3127



3128
3129
3130
3131
3132
3133
3134
    }



    # format expression and result with LaTeX
    # return a list of:
    #   LaTeX formatted expression
    #   result of calculation (as an expression string)
    #   result magnitude
    #     (this number may need formatting for significant digets)
    #   LaTeX formatted result units expression (may be empty string)
    #   repeat last two items for each additional set of requested units

    proc ltxExpr {str {locals ""}} {
	# evaluate an expression
	# return result as valid unit expression

	set callingLevel #[expr {[info level] - 1}]
	# segment expression at = (skipping == != <= ...)
	set segs [segmentExpr $str]
	set rucmds [lassign  \
			[lmap seg $segs {
			    if {"" ne $seg} {tclizeExpr $seg} else {list}
			}]  \
			ecmd]
	# evaluate first segment
	set val [namespace eval eval $ecmd]
	set opt [list [namespace eval ltx $ecmd]]
	# save result of expression
	lappend opt [unitizeResult $callingLevel $val [lindex $segs 1]]
	# if no units are requested, no result is displayed
	# a trailing "=" displays result in basis units (or none)



	foreach runit $rucmds {
	    # if there are requested units
	    if {"" ne $runit} {
		# divide result by requested units
		set rval [eval::/ $val [namespace eval eval  $runit]]
		# get requested units string
		set runit [namespace eval ltx $runit]
2983
2984
2985
2986
2987
2988
2989

2990
2991
2992
2993
2994
2995
2996
2997
2998
2999

    # define a new unit

    proc newUnit {name expr} {
	variable units

	set callingLevel #[expr [info level] - 1]

	if {[catch {set units($name)} u]} {
	    # its a new one, define it
	    set units($name) [uExpr #[expr {[info level] - 1}] $expr]
	} else {
	    # its already defined
	    error "newUnit: $name is already [uStr $u]"
	}
    }









>


|







3159
3160
3161
3162
3163
3164
3165
3166
3167
3168
3169
3170
3171
3172
3173
3174
3175
3176

    # define a new unit

    proc newUnit {name expr} {
	variable units

	set callingLevel #[expr [info level] - 1]
	set locals {}
	if {[catch {set units($name)} u]} {
	    # its a new one, define it
	    set units($name) [namespace eval eval [tclizeExpr $expr]]
	} else {
	    # its already defined
	    error "newUnit: $name is already [uStr $u]"
	}
    }


3033
3034
3035
3036
3037
3038
3039
3040


3041
3042
3043
3044
3045
3046
3047
	


    proc unitsLike {{expr 1}} {
	# return a sorted list of all units and constants
	# with the same dimensions as the expression
	variable units
	set uval [uExpr #[expr {[info level] - 1}] $expr]


	lsort -dictionary [lmap {key value} [array get units] {
	    if {[SameUnits $uval $value]} {
		set key
	    } else {
		continue
	    }
	}]







|
>
>







3210
3211
3212
3213
3214
3215
3216
3217
3218
3219
3220
3221
3222
3223
3224
3225
3226
	


    proc unitsLike {{expr 1}} {
	# 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 uval [namespace eval eval [tclizeExpr $expr]]
	lsort -dictionary [lmap {key value} [array get units] {
	    if {[SameUnits $uval $value]} {
		set key
	    } else {
		continue
	    }
	}]
3174
3175
3176
3177
3178
3179
3180
3181
3182
3183
3184
3185
3186
3187
3188
    newUnit inch ft/12.0
    newUnit in inch
    newUnit yard 3*ft
    newUnit yd yard
    newUnit mile 5280*ft
    newUnit mi mile
    # factor to convert survey lengths to statute lengths (ft, miles, etc)
    newUnit survey (1200/3937)*meter/ft
    newUnit statute 1.0

    # based on 1 inch=2.54 cm
    newUnit m meter
    newUnit cm centi*meter
    newUnit mm milli*meter
    newUnit km kilo*meter







|







3353
3354
3355
3356
3357
3358
3359
3360
3361
3362
3363
3364
3365
3366
3367
    newUnit inch ft/12.0
    newUnit in inch
    newUnit yard 3*ft
    newUnit yd yard
    newUnit mile 5280*ft
    newUnit mi mile
    # factor to convert survey lengths to statute lengths (ft, miles, etc)
    newUnit survey "1200 meter/3937 ft"
    newUnit statute 1.0

    # based on 1 inch=2.54 cm
    newUnit m meter
    newUnit cm centi*meter
    newUnit mm milli*meter
    newUnit km kilo*meter

Changes to uexpr.test.

50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
    variable SETUP {}

    variable CLEANUP {}


    test unitCompare {} -body {
	unitCompare 1 1
    } -result 1
    
    test unitCompare-2 {} -body {
	unitCompare 1 0
    } -result 0
    
    test unitCompare-3 {} -body {
	unitCompare 0*psi 0*bar
    } -result 1
    
    test unitCompare-4 {} -body {
	variable eps
	unitCompare 2*psi (2+eps)*psi
    } -result 1
    
    
    test debug-1 {} -setup $SETUP -body {
	# verifies dm does not error or display the message
	uexpr::dm "test dm"
    } -result "" -output ""








|



|



|




|







50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
    variable SETUP {}

    variable CLEANUP {}


    test unitCompare {} -body {
	unitCompare 1 1
    } -result 1.0
    
    test unitCompare-2 {} -body {
	unitCompare 1 0
    } -result 0.0
    
    test unitCompare-3 {} -body {
	unitCompare 0*psi 0*bar
    } -result 1.0
    
    test unitCompare-4 {} -body {
	variable eps
	unitCompare 2*psi (2+eps)*psi
    } -result 1.0
    
    
    test debug-1 {} -setup $SETUP -body {
	# verifies dm does not error or display the message
	uexpr::dm "test dm"
    } -result "" -output ""

359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387


    test ltx::$-1 {} -setup {
	catch {unset a}
	set a 3*in
    } -body {
	uexpr::ltxExpr {3$a}
    } -cleanup {unset a} -result {{3\,\mathrm{a}} {0.75 ft} 0.75 {\cdot \mathrm{ft}}}

    test ltx::$-2 {} -setup {catch {unset a}} -body {
	uexpr::ltxExpr {3$a}
    }  -returnCodes 1  -match glob  -result {*a is not a variable*}

    test ltx::$-3 {} -setup {catch {unset ft}} -body {
	uexpr::ltxExpr {3$ft}
    }  -returnCodes 1  -match glob  -result {*ft is not a variable*}



    test ltx:::-1 {} -setup $SETUP -body {
	uexpr::ltxExpr {3:ft}
    }  -result {{3\,\mathrm{ft}} {3.0 ft} 3.0 {\cdot \mathrm{ft}}}

    test ltx:::-2 {} -setup {catch {set aaa 3*in}} -body {
	uexpr::ltxExpr {3:aaa}
    }  -cleanup {unset aaa}  -returnCodes 1  -match glob  -result {*aaa is not a unit*}










|













|







359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387


    test ltx::$-1 {} -setup {
	catch {unset a}
	set a 3*in
    } -body {
	uexpr::ltxExpr {3$a}
    } -cleanup {unset a} -result {{3\,\mathrm{a}} {0.75 ft}}

    test ltx::$-2 {} -setup {catch {unset a}} -body {
	uexpr::ltxExpr {3$a}
    }  -returnCodes 1  -match glob  -result {*a is not a variable*}

    test ltx::$-3 {} -setup {catch {unset ft}} -body {
	uexpr::ltxExpr {3$ft}
    }  -returnCodes 1  -match glob  -result {*ft is not a variable*}



    test ltx:::-1 {} -setup $SETUP -body {
	uexpr::ltxExpr {3:ft}
    }  -result {{3\,\mathrm{ft}} {3.0 ft}}

    test ltx:::-2 {} -setup {catch {set aaa 3*in}} -body {
	uexpr::ltxExpr {3:aaa}
    }  -cleanup {unset aaa}  -returnCodes 1  -match glob  -result {*aaa is not a unit*}



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


    
    # LaTeX for arrays
    
    test ltxArray-1 {} -setup $arraySetup -body {
	uexpr::ltxExpr vv..0
    }  -cleanup $arrayCleanup  -result {{\vec{\mathrm{vv}}_{0}} 1 1 {}}
    
    test ltxArray-2 {} -setup $arraySetup -body {
	lindex [uexpr::ltxExpr vv..4] 1
    }  -cleanup $arrayCleanup  -result 5
    
    test ltxArray-3 {} -setup $arraySetup -body {
	lindex [uexpr::ltxExpr vv..0+1] 1
    }  -cleanup $arrayCleanup  -result 2
    
    test ltxArray-4 {} -setup $arraySetup -body {
	lindex [uexpr::ltxExpr vv..(1+3)] 1
    }  -cleanup $arrayCleanup  -result 5
    
    test ltxArray-5 {} -setup $arraySetup -body {
	set i 2
	lindex [uexpr::ltxExpr vv..i] 2
    }  -cleanup $arrayCleanup  -result 3
    
    test ltxArray-6 {} -setup $arraySetup -body {
	uexpr::ltxExpr vv..5
    }  -cleanup $arrayCleanup  -returnCodes error  \
	-match glob  -result *
    







|















|







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


    
    # LaTeX for arrays
    
    test ltxArray-1 {} -setup $arraySetup -body {
	uexpr::ltxExpr vv..0
    }  -cleanup $arrayCleanup  -result {{\vec{\mathrm{vv}}_{0}} 1}
    
    test ltxArray-2 {} -setup $arraySetup -body {
	lindex [uexpr::ltxExpr vv..4] 1
    }  -cleanup $arrayCleanup  -result 5
    
    test ltxArray-3 {} -setup $arraySetup -body {
	lindex [uexpr::ltxExpr vv..0+1] 1
    }  -cleanup $arrayCleanup  -result 2
    
    test ltxArray-4 {} -setup $arraySetup -body {
	lindex [uexpr::ltxExpr vv..(1+3)] 1
    }  -cleanup $arrayCleanup  -result 5
    
    test ltxArray-5 {} -setup $arraySetup -body {
	set i 2
	lindex [uexpr::ltxExpr vv..i=] 2
    }  -cleanup $arrayCleanup  -result 3
    
    test ltxArray-6 {} -setup $arraySetup -body {
	uexpr::ltxExpr vv..5
    }  -cleanup $arrayCleanup  -returnCodes error  \
	-match glob  -result *
    
1122
1123
1124
1125
1126
1127
1128
























1129
1130
1131
1132
1133
1134
1135
    }  -cleanup $CLEANUP  -result {\mathrm{atan}\left ( 0.5 \right ) }

    test atan-4 {} -setup $SETUP -body {
	lindex [uexpr::ltxExpr atan(30*deg)] 0
    }  -cleanup $CLEANUP  -returnCodes error  \
	-match  glob  -result {*operand*cannot have units*}


























    test cos {} -setup $SETUP  -body {
	uexpr cos(60*deg)
    } -cleanup $CLEANUP  -match real  -result 0.5

    test cos-1 {} -setup $SETUP  -body {
	uexpr cos(0*deg)







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
    }  -cleanup $CLEANUP  -result {\mathrm{atan}\left ( 0.5 \right ) }

    test atan-4 {} -setup $SETUP -body {
	lindex [uexpr::ltxExpr atan(30*deg)] 0
    }  -cleanup $CLEANUP  -returnCodes error  \
	-match  glob  -result {*operand*cannot have units*}


    test atan2 {} -setup $SETUP  -body {
	uexpr atan2(1in,1in)/deg
    } -cleanup $CLEANUP  -match real  -result 45

    test atan2-1 {} -setup $SETUP  -body {
	uexpr atan2(0ft,1ft)/deg
    } -cleanup $CLEANUP  -match real  -result 0

    # remember 0 deg is along +x axis (map east!) here
    test atan2-2 {} -setup $SETUP  -body {
	uexpr atan2(1ft,0ft)/deg
    } -cleanup $CLEANUP  -match real  -result 90
    
    test atan2-3 {} -setup $SETUP -body {
	lindex [uexpr::ltxExpr atan2(1cm,2cm)] 0
    }  -cleanup $CLEANUP   \
	-result {\mathrm{atan2}\left ( 1\,\mathrm{cm}, 2\,\mathrm{cm} \right ) }

    test atan2-4 {} -setup $SETUP -body {
	lindex [uexpr::ltxExpr atan2(30in,5sec)] 0
    }  -cleanup $CLEANUP  -returnCodes error  \
	-match  glob  -result {*different units*}


    test cos {} -setup $SETUP  -body {
	uexpr cos(60*deg)
    } -cleanup $CLEANUP  -match real  -result 0.5

    test cos-1 {} -setup $SETUP  -body {
	uexpr cos(0*deg)
1564
1565
1566
1567
1568
1569
1570









































1571
1572
1573
1574
1575
1576
1577
    }  -cleanup $CLEANUP  -result {\mathrm{max}\left ( 1, 3, -5, 2 \right ) }

    test max-6 {} -setup $SETUP -body {
	uexpr max(55*ft,-7 sec,-3,1234)
    } -cleanup $CLEANUP  -returnCodes error \
	-match glob -result {*all operands must have the same units*}










































    
    test hasUnits {} -setup $SETUP -body {
	uexpr hasUnits(psi)
    } -cleanup $CLEANUP  -result 1

    test hasUnits-2 {} -setup $SETUP -body {
	uexpr hasUnits(-55)







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
    }  -cleanup $CLEANUP  -result {\mathrm{max}\left ( 1, 3, -5, 2 \right ) }

    test max-6 {} -setup $SETUP -body {
	uexpr max(55*ft,-7 sec,-3,1234)
    } -cleanup $CLEANUP  -returnCodes error \
	-match glob -result {*all operands must have the same units*}



    test sum-1 {} -setup $SETUP -body {
	uexpr sum(i,0,4,2^i)
    } -cleanup $CLEANUP  -match real  -result 31.0 \

    test sum-2 {} -setup $SETUP -body {
	uexpr sum(i,1in,4,2^i)
    } -cleanup $CLEANUP  -returnCodes error \
	-match glob -result {*sum initial index value must be unitless*}

    test sum-3 {} -setup $SETUP -body {
	uexpr sum(i,1,4sec,2^i)
    } -cleanup $CLEANUP  -returnCodes error \
	-match glob -result {*sum final index value must be unitless*}

    # the important part is there was an error due to the badly formed expression
    # the error message is ugly and not very informative
    test sum-4 {} -setup $SETUP -body {
	uexpr sum(i,0,4,2^)
     } -cleanup $CLEANUP  -returnCodes error \
	-match glob -result {*}

    test sum-5 {} -setup $SETUP -body {
	set c [uexpr 1,2,3,4,5]
	set x [uexpr 0.2]
	uexpr sum(i,-1,4,c..i*x^i)
    } -cleanup $CLEANUP  -returnCodes error \
	-match glob -result {*array c has no value for index -1*}

    test sum-6 {} -setup $SETUP -body {
	set c [uexpr 1,2,3,4,5]
	set x [uexpr 0.2]
	uexpr sum(i,0,4,c..i*x^i)
    } -cleanup $CLEANUP  -match real  -result 1.56 \

    test sum-7 {} -setup $SETUP  -body {
	::uexpr::ltxExpr sum(i,0,4,2^i)
    } -cleanup $CLEANUP   \
	-result {{\sum_{i=0}^{4} 2^{\mathrm{i}}} 31.0} 

    
    test hasUnits {} -setup $SETUP -body {
	uexpr hasUnits(psi)
    } -cleanup $CLEANUP  -result 1

    test hasUnits-2 {} -setup $SETUP -body {
	uexpr hasUnits(-55)
1740
1741
1742
1743
1744
1745
1746














1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
	lindex [uexpr::ltxExpr 4++-5] 0
    } -cleanup $CLEANUP -result { \begin{array}{l} 4 + \\ \mbox{} + -5 \end{array} }

    test +CR-8 {} -setup $SETUP -body {
	lindex [uexpr::ltxExpr (4-1)++-(5-3)] 0
    } -cleanup $CLEANUP -result { \begin{array}{l}  \left ( 4-1 \right )  + \\ \mbox{} + - \left ( 5-3 \right )  \end{array} }















    
    
    # 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 4 -- a
    } -cleanup $CLEANUP -result -2

    # 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 {*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 {







>
>
>
>
>
>
>
>
>
>
>
>
>
>

















|







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
	lindex [uexpr::ltxExpr 4++-5] 0
    } -cleanup $CLEANUP -result { \begin{array}{l} 4 + \\ \mbox{} + -5 \end{array} }

    test +CR-8 {} -setup $SETUP -body {
	lindex [uexpr::ltxExpr (4-1)++-(5-3)] 0
    } -cleanup $CLEANUP -result { \begin{array}{l}  \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}{l} 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}{l} 4 + \\ \mbox{} + 5 - \\ \mbox{} - 6 \end{array} }

    test +CR-11 {} -setup $SETUP -body {
	uexpr 4 ++ 5 ++ 6 -- 7
    } -cleanup $CLEANUP -result 8

    
    
    # 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 4 -- a
    } -cleanup $CLEANUP -result -2

    # 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 subtract 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 {
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089




















2090
2091
2092
2093
2094
2095
2096
    } -cleanup $CLEANUP  -match real  -result -32    
    
    
    # comparisons

    test <=-1 {} -setup $SETUP -body {
	uexpr 3*ft <= 4*ft
    } -cleanup $CLEANUP -result 1

    test <=-2 {} -setup $SETUP -body {
	uexpr 5 <= 4
    } -cleanup $CLEANUP -result 0

    test <=-3 {} -setup $SETUP -body {
	uexpr 3 <= 3
    } -cleanup $CLEANUP -result 1

    test <=-4 {} -setup $SETUP -body {
	uexpr 5 <= 4*psi
    } -cleanup $CLEANUP -match glob -result "*different units*" \
	-returnCodes error

    test <=-5 {} -setup $SETUP -body {
	lindex [uexpr::ltxExpr 3<=5] 0
    } -cleanup $CLEANUP  -result {3 \leq 5}


    test =<-1 {} -setup $SETUP -body {
	uexpr 3 =< 4
    } -cleanup $CLEANUP -result 1

    test =<-2 {} -setup $SETUP -body {
	uexpr 5 =< 4
    } -cleanup $CLEANUP -result 0

    test =<-3 {} -setup $SETUP -body {
	uexpr 3 =< 3
    } -cleanup $CLEANUP -result 1

    test =<-4 {} -setup $SETUP -body {
	uexpr 5 =< 4*psi
    } -cleanup $CLEANUP -match glob -result "*different units*" \
	-returnCodes error

    test =<-5 {} -setup $SETUP -body {
	lindex [uexpr::ltxExpr 3=<5] 0
    } -cleanup $CLEANUP  -result {3 \leq 5}


    test >=-1 {} -setup $SETUP -body {
	uexpr 3 >= 4
    } -cleanup $CLEANUP -result 0

    test >=-2 {} -setup $SETUP -body {
	uexpr 5 >= 4
    } -cleanup $CLEANUP -result 1

    test >=-3 {} -setup $SETUP -body {
	uexpr 4 >= 4
    } -cleanup $CLEANUP -result 1

    test >=-4 {} -setup $SETUP -body {
	uexpr 5 >= 4*psi
    } -cleanup $CLEANUP -match glob -result "*different units*" \
	-returnCodes error

    test >=-5 {} -setup $SETUP -body {
	lindex [uexpr::ltxExpr 3>=5] 0
    } -cleanup $CLEANUP  -result {3 \geq 5}


    test =>-1 {} -setup $SETUP -body {
	uexpr 3 => 4
    } -cleanup $CLEANUP -result 0

    test =>-2 {} -setup $SETUP -body {
	uexpr 5 => 4
    } -cleanup $CLEANUP -result 1

    test =>-3 {} -setup $SETUP -body {
	uexpr 4 => 4
    } -cleanup $CLEANUP -result 1

    test =>-4 {} -setup $SETUP -body {
	uexpr 5 => 4*psi
    } -cleanup $CLEANUP -match glob -result "*different units*" \
	-returnCodes error
    
    test =>-5 {} -setup $SETUP -body {
	lindex [uexpr::ltxExpr 3=>5] 0
    } -cleanup $CLEANUP  -result {3 \geq 5}


    test ==-1 {} -setup $SETUP -body {
	uexpr 3 == 4
    } -cleanup $CLEANUP -result 0

    test ==-2 {} -setup $SETUP -body {
	uexpr 5 == 5
    } -cleanup $CLEANUP -result 1

    test ==-3 {} -setup $SETUP -body {
	uexpr 5 == 4*psi
    } -cleanup $CLEANUP -match glob -result "*different units*" \
	-returnCodes error

    test ==-5 {} -setup $SETUP -body {
	lindex [uexpr::ltxExpr 3==5] 0
    } -cleanup $CLEANUP  -result {3=5}


    test !=-1 {} -setup $SETUP -body {
	uexpr 3 != 4
    } -cleanup $CLEANUP -result 1

    test !=-2 {} -setup $SETUP -body {
	uexpr 5 != 5
    } -cleanup $CLEANUP -result 0

    test !=-3 {} -setup $SETUP -body {
	uexpr 5 != 4*psi
    } -cleanup $CLEANUP -match glob -result "*different units*" \
	-returnCodes error

    test !=-5 {} -setup $SETUP -body {
	lindex [uexpr::ltxExpr 3!=5] 0
    } -cleanup $CLEANUP  -result {3\neq 5}






















    # probably should be an error for now
    test group-1 {} -setup $SETUP -body {
	uexpr 2*(3,5,7)
    }  -cleanup $CLEANUP  -returnCodes error -match glob -result *








|



|



|













|



|



|













|



|



|













|



|



|













|



|








|




|



|








|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
    } -cleanup $CLEANUP  -match real  -result -32    
    
    
    # comparisons

    test <=-1 {} -setup $SETUP -body {
	uexpr 3*ft <= 4*ft
    } -cleanup $CLEANUP -result 1.0

    test <=-2 {} -setup $SETUP -body {
	uexpr 5 <= 4
    } -cleanup $CLEANUP -result 0.0

    test <=-3 {} -setup $SETUP -body {
	uexpr 3 <= 3
    } -cleanup $CLEANUP -result 1.0

    test <=-4 {} -setup $SETUP -body {
	uexpr 5 <= 4*psi
    } -cleanup $CLEANUP -match glob -result "*different units*" \
	-returnCodes error

    test <=-5 {} -setup $SETUP -body {
	lindex [uexpr::ltxExpr 3<=5] 0
    } -cleanup $CLEANUP  -result {3 \leq 5}


    test =<-1 {} -setup $SETUP -body {
	uexpr 3 =< 4
    } -cleanup $CLEANUP -result 1.0

    test =<-2 {} -setup $SETUP -body {
	uexpr 5 =< 4
    } -cleanup $CLEANUP -result 0.0

    test =<-3 {} -setup $SETUP -body {
	uexpr 3 =< 3
    } -cleanup $CLEANUP -result 1.0

    test =<-4 {} -setup $SETUP -body {
	uexpr 5 =< 4*psi
    } -cleanup $CLEANUP -match glob -result "*different units*" \
	-returnCodes error

    test =<-5 {} -setup $SETUP -body {
	lindex [uexpr::ltxExpr 3=<5] 0
    } -cleanup $CLEANUP  -result {3 \leq 5}


    test >=-1 {} -setup $SETUP -body {
	uexpr 3 >= 4
    } -cleanup $CLEANUP -result 0.0

    test >=-2 {} -setup $SETUP -body {
	uexpr 5 >= 4
    } -cleanup $CLEANUP -result 1.0

    test >=-3 {} -setup $SETUP -body {
	uexpr 4 >= 4
    } -cleanup $CLEANUP -result 1.0

    test >=-4 {} -setup $SETUP -body {
	uexpr 5 >= 4*psi
    } -cleanup $CLEANUP -match glob -result "*different units*" \
	-returnCodes error

    test >=-5 {} -setup $SETUP -body {
	lindex [uexpr::ltxExpr 3>=5] 0
    } -cleanup $CLEANUP  -result {3 \geq 5}


    test =>-1 {} -setup $SETUP -body {
	uexpr 3 => 4
    } -cleanup $CLEANUP -result 0.0

    test =>-2 {} -setup $SETUP -body {
	uexpr 5 => 4
    } -cleanup $CLEANUP -result 1.0

    test =>-3 {} -setup $SETUP -body {
	uexpr 4 => 4
    } -cleanup $CLEANUP -result 1.0

    test =>-4 {} -setup $SETUP -body {
	uexpr 5 => 4*psi
    } -cleanup $CLEANUP -match glob -result "*different units*" \
	-returnCodes error
    
    test =>-5 {} -setup $SETUP -body {
	lindex [uexpr::ltxExpr 3=>5] 0
    } -cleanup $CLEANUP  -result {3 \geq 5}


    test ==-1 {} -setup $SETUP -body {
	uexpr 3 == 4
    } -cleanup $CLEANUP -result 0.0

    test ==-2 {} -setup $SETUP -body {
	uexpr 5 == 5
    } -cleanup $CLEANUP -result 1.0

    test ==-3 {} -setup $SETUP -body {
	uexpr 5 == 4*psi
    } -cleanup $CLEANUP -match glob -result "*different units*" \
	-returnCodes error

    test ==-5 {} -setup $SETUP -body {
	lindex [uexpr::ltxExpr 3==5] 0
    } -cleanup $CLEANUP  -result {3 = 5}


    test !=-1 {} -setup $SETUP -body {
	uexpr 3 != 4
    } -cleanup $CLEANUP -result 1.0

    test !=-2 {} -setup $SETUP -body {
	uexpr 5 != 5
    } -cleanup $CLEANUP -result 0.0

    test !=-3 {} -setup $SETUP -body {
	uexpr 5 != 4*psi
    } -cleanup $CLEANUP -match glob -result "*different units*" \
	-returnCodes error

    test !=-5 {} -setup $SETUP -body {
	lindex [uexpr::ltxExpr 3!=5] 0
    } -cleanup $CLEANUP  -result {3 \neq 5}


    test chained-compare {}  -setup $SETUP -body {
	uexpr 1<2<3
    } -cleanup $CLEANUP  -result 1.0


    test chained-compare-2 {}  -setup $SETUP -body {
	uexpr 1<2<=3==3>2>=1!=7
    } -cleanup $CLEANUP  -result 1.0


    test chained-compare-3 {}  -setup $SETUP -body {
	uexpr 3psi>100Pa<=1000lbf/ft^2
    } -cleanup $CLEANUP  -result 1.0

    test chained-compare-4 {}  -setup $SETUP -body {
	uexpr 3in<1sec>3cm
    } -cleanup $CLEANUP -match glob -result "*different units*" \
	-returnCodes error


    # probably should be an error for now
    test group-1 {} -setup $SETUP -body {
	uexpr 2*(3,5,7)
    }  -cleanup $CLEANUP  -returnCodes error -match glob -result *

2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
    test baseUnitStr-7 {} -setup $SETUP -body {
	llength [uexpr::baseUnitStr [lrepeat 100 2]]
    }  -cleanup $CLEANUP  -returnCodes error \
	-match glob -result "*too many unit exponents in*"
    
    test baseUnitExpStr {} -setup $SETUP -body {
	uexpr::baseUnitExpStr {1 2 3}
    }   -cleanup $CLEANUP -result { lbm ft^2 sec^3}
    
    test baseUnitExpStr-1 {} -setup $SETUP -body {
	uexpr::baseUnitExpStr {1 -2 3}
    }  -cleanup $CLEANUP  -result { lbm ft^-2 sec^3}
    
    test baseUnitExpStr-2 {} -setup $SETUP -body {
	uexpr::baseUnitExpStr {-1 -2 -3}
    }  -cleanup $CLEANUP  -result { lbm^-1 ft^-2 sec^-3}
    
    test baseUnitExpStr-3 {} -setup $SETUP -body {
	uexpr::baseUnitExpStr {-.00001 -2 -3}
    }  -cleanup $CLEANUP  -result { ft^-2 sec^-3}
    
    test baseUnitExpStr-3.1 {} -setup $SETUP -body {
	uexpr::baseUnitExpStr {.00001 2 -3}
    }  -cleanup $CLEANUP  -result { ft^2 sec^-3}
    
    test baseUnitExpStr-4 {} -setup $SETUP -body {
	uexpr::baseUnitExpStr {1 "" -3}
    }  -cleanup $CLEANUP  -result { lbm}
    
    test baseUnitExpStr-5 {} -setup $SETUP -body {
	uexpr::baseUnitExpStr {-1 "" -3}
    }  -cleanup $CLEANUP  -result { lbm^-1}
    
    test baseUnitExpStr-6 {} -setup $SETUP -body {
	uexpr::baseUnitExpStr {}
    }  -cleanup $CLEANUP  -result {}
    
    test baseUnitExpStr-7 {} -setup $SETUP -body {
	llength [uexpr::baseUnitExpStr [lrepeat 100 2]]
    }  -cleanup $CLEANUP  -returnCodes error \
	-match glob -result "*too many unit exponents in*"
    

    test ltxBaseUnitExpStr {} -setup $SETUP -body {
	uexpr::ltxBaseUnitExpStr {1 2 3}
    }   -cleanup $CLEANUP  \
	-result {\cdot \mathrm{lbm}\cdot \mathrm{ft}^2\cdot \mathrm{sec}^3}
    
    test ltxBaseUnitExpStr-1 {} -setup $SETUP -body {
	uexpr::ltxBaseUnitExpStr {1 -2 3}
    }  -cleanup $CLEANUP  \
	-result {\cdot \mathrm{lbm}\cdot \mathrm{ft}^-2\cdot \mathrm{sec}^3}
    
    test ltxBaseUnitExpStr-2 {} -setup $SETUP -body {
	uexpr::ltxBaseUnitExpStr {-1 -2 -3}
    }  -cleanup $CLEANUP  \
	-result {\cdot \mathrm{lbm}^-1\cdot \mathrm{ft}^-2\cdot \mathrm{sec}^-3}
    
    test ltxBaseUnitExpStr-3 {} -setup $SETUP -body {
	uexpr::ltxBaseUnitExpStr {-.00001 -2 -3}
    }  -cleanup $CLEANUP  -result {\cdot \mathrm{ft}^-2\cdot \mathrm{sec}^-3}
    
    test ltxBaseUnitExpStr-3.1 {} -setup $SETUP -body {
	uexpr::ltxBaseUnitExpStr {.00001 2 -3}
    }  -cleanup $CLEANUP  -result {\cdot \mathrm{ft}^2\cdot \mathrm{sec}^-3}
    
    test ltxBaseUnitExpStr-4 {} -setup $SETUP -body {
	uexpr::ltxBaseUnitExpStr {1 "" -3}
    }  -cleanup $CLEANUP  -result {\cdot \mathrm{lbm}}

    test ltxBaseUnitExpStr-5 {} -setup $SETUP -body {
	uexpr::ltxBaseUnitExpStr {-1 "" -3}
    }  -cleanup $CLEANUP  -result {\cdot \mathrm{lbm}^-1}

    test ltxBaseUnitExpStr-6 {} -setup $SETUP -body {
	uexpr::ltxBaseUnitExpStr {}
    }  -cleanup $CLEANUP  -result {}
    
    test ltxBaseUnitExpStr-7 {} -setup $SETUP -body {
	llength [uexpr::ltxBaseUnitExpStr [lrepeat 100 2]]







|



|



|



|



|



|



|














|




|




|



|



|







|







2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
    test baseUnitStr-7 {} -setup $SETUP -body {
	llength [uexpr::baseUnitStr [lrepeat 100 2]]
    }  -cleanup $CLEANUP  -returnCodes error \
	-match glob -result "*too many unit exponents in*"
    
    test baseUnitExpStr {} -setup $SETUP -body {
	uexpr::baseUnitExpStr {1 2 3}
    }   -cleanup $CLEANUP -result {*lbm*ft^2*sec^3}
    
    test baseUnitExpStr-1 {} -setup $SETUP -body {
	uexpr::baseUnitExpStr {1 -2 3}
    }  -cleanup $CLEANUP  -result {*lbm/ft^2*sec^3}
    
    test baseUnitExpStr-2 {} -setup $SETUP -body {
	uexpr::baseUnitExpStr {-1 -2 -3}
    }  -cleanup $CLEANUP  -result {/lbm/ft^2/sec^3}
    
    test baseUnitExpStr-3 {} -setup $SETUP -body {
	uexpr::baseUnitExpStr {-.00001 -2 -3}
    }  -cleanup $CLEANUP  -result {/ft^2/sec^3}
    
    test baseUnitExpStr-3.1 {} -setup $SETUP -body {
	uexpr::baseUnitExpStr {.00001 2 -3}
    }  -cleanup $CLEANUP  -result {*ft^2/sec^3}
    
    test baseUnitExpStr-4 {} -setup $SETUP -body {
	uexpr::baseUnitExpStr {1 "" -3}
    }  -cleanup $CLEANUP  -result {*lbm}
    
    test baseUnitExpStr-5 {} -setup $SETUP -body {
	uexpr::baseUnitExpStr {-1 "" -3}
    }  -cleanup $CLEANUP  -result {/lbm}
    
    test baseUnitExpStr-6 {} -setup $SETUP -body {
	uexpr::baseUnitExpStr {}
    }  -cleanup $CLEANUP  -result {}
    
    test baseUnitExpStr-7 {} -setup $SETUP -body {
	llength [uexpr::baseUnitExpStr [lrepeat 100 2]]
    }  -cleanup $CLEANUP  -returnCodes error \
	-match glob -result "*too many unit exponents in*"
    

    test ltxBaseUnitExpStr {} -setup $SETUP -body {
	uexpr::ltxBaseUnitExpStr {1 2 3}
    }   -cleanup $CLEANUP  \
	-result {\cdot \mathrm{lbm}\cdot \mathrm{ft}^{2}\cdot \mathrm{sec}^{3}}
    
    test ltxBaseUnitExpStr-1 {} -setup $SETUP -body {
	uexpr::ltxBaseUnitExpStr {1 -2 3}
    }  -cleanup $CLEANUP  \
	-result {\cdot \mathrm{lbm}\cdot \mathrm{ft}^{-2}\cdot \mathrm{sec}^{3}}
    
    test ltxBaseUnitExpStr-2 {} -setup $SETUP -body {
	uexpr::ltxBaseUnitExpStr {-1 -2 -3}
    }  -cleanup $CLEANUP  \
	-result {\cdot \mathrm{lbm}^{-1}\cdot \mathrm{ft}^{-2}\cdot \mathrm{sec}^{-3}}
    
    test ltxBaseUnitExpStr-3 {} -setup $SETUP -body {
	uexpr::ltxBaseUnitExpStr {-.00001 -2 -3}
    }  -cleanup $CLEANUP  -result {\cdot \mathrm{ft}^{-2}\cdot \mathrm{sec}^{-3}}
    
    test ltxBaseUnitExpStr-3.1 {} -setup $SETUP -body {
	uexpr::ltxBaseUnitExpStr {.00001 2 -3}
    }  -cleanup $CLEANUP  -result {\cdot \mathrm{ft}^{2}\cdot \mathrm{sec}^{-3}}
    
    test ltxBaseUnitExpStr-4 {} -setup $SETUP -body {
	uexpr::ltxBaseUnitExpStr {1 "" -3}
    }  -cleanup $CLEANUP  -result {\cdot \mathrm{lbm}}

    test ltxBaseUnitExpStr-5 {} -setup $SETUP -body {
	uexpr::ltxBaseUnitExpStr {-1 "" -3}
    }  -cleanup $CLEANUP  -result {\cdot \mathrm{lbm}^{-1}}

    test ltxBaseUnitExpStr-6 {} -setup $SETUP -body {
	uexpr::ltxBaseUnitExpStr {}
    }  -cleanup $CLEANUP  -result {}
    
    test ltxBaseUnitExpStr-7 {} -setup $SETUP -body {
	llength [uexpr::ltxBaseUnitExpStr [lrepeat 100 2]]
2344
2345
2346
2347
2348
2349
2350












2351
2352
2353
2354
2355
2356
2357
	uexpr 3ft=cm=in=
    } -result {3.0 ft}

    test uexpr-ureq3 {} -setup $SETUP  -body {
	uexpr::ltxExpr "3ft= =in"
    } -result {{3\,\mathrm{ft}} {3.0 ft} 3.0 {\cdot \mathrm{ft}} 36.0 {\mathrm{in}}}














    

    cleanupTests
}

#namespace delete ::uexpr::test







>
>
>
>
>
>
>
>
>
>
>
>







2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
	uexpr 3ft=cm=in=
    } -result {3.0 ft}

    test uexpr-ureq3 {} -setup $SETUP  -body {
	uexpr::ltxExpr "3ft= =in"
    } -result {{3\,\mathrm{ft}} {3.0 ft} 3.0 {\cdot \mathrm{ft}} 36.0 {\mathrm{in}}}



    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