uexpr

Check-in [994f7b267f]
Login

Check-in [994f7b267f]

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

Overview
Comment:array members only accessed using $, clean up lists, fix errors in ++ --, update manpage
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | trunk
Files: files | file ages | folders
SHA3-256: 994f7b267f3a82643c43e6a758a0e1a5f37f761a83c66c41780dff64ac7e4a92
User & Date: davebr 2024-05-09 16:19:52
Context
2024-05-09
16:19
array members only accessed using $, clean up lists, fix errors in ++ --, update manpage Leaf check-in: 994f7b267f user: davebr tags: trunk
2024-04-06
13:42
works,, list/vector/tcl array access will change check-in: f7694b2246 user: davebr tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to Makefile.

1


2
3
4
5
6
7
8
# Make uexpr documentation, run tests



all : test doc

doc : uexpr.n uexpr.html


test : uexpr.tcl uexpr.test

>
>







1
2
3
4
5
6
7
8
9
10
# Make uexpr documentation, run tests
# This works on my ststem.
# The install directories need to be checked anywhere else.

all : test doc

doc : uexpr.n uexpr.html


test : uexpr.tcl uexpr.test

Changes to uexpr.dt.

1
2
3
4
5
6
7
8
9
10
[manpage_begin uexpr n 0.1]
[moddesc   {Calculations with Units}]
[copyright {2021 J.D Bruchie (BSD License)}]
[titledesc {A Tcl package to evaluate and format expressions that may include values with units}]
[require Tcl  8.6]
[require uexpr [opt 0.1]]
[require uexpr= [opt 0.1]]

[description]



|







1
2
3
4
5
6
7
8
9
10
[manpage_begin uexpr n 0.1]
[moddesc   {Calculations with Units}]
[copyright {2021-2024 J.D Bruchie (BSD License)}]
[titledesc {A Tcl package to evaluate and format expressions that may include values with units}]
[require Tcl  8.6]
[require uexpr [opt 0.1]]
[require uexpr= [opt 0.1]]

[description]

32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51



52
53
54
55
56
57
58
59
60
Operands can be numbers or numbers with units, sometimes saved in
variables or a units dictionary. [package uexpr] does not operate on
strings.  Comparisons in [package uexpr] expressions return a numeric
1.0 for true, or 0.0 for false.

[subsection Numbers]

Numbers start with a decimal point or digit, and may include only one decimal point..
[example "1 1.23 .123"]
They may include a 10's exponent after an e or E. The exponent may be signed.
[example "1e-3 == 0.001"]
Numbers can start after most operators or a space.
[package uexpr] treats all numbers as double precision reals.

When a string can be interpreted as a number it will be.

[example "3e+5e == 300000*e"]
The first "e" is part of a number. The second e is interpreted as a variable named e. A number followed by a variable with no operator in between implies a multiplication.

[example "3e +5e == 3*e+5*e"] [example "3E+ 5e == 3*E+5*e"] Here the



space breaks up the scientific number definition, so both "e" and "E"
are interpreted as variables or units.

Keep this in mind if scientific notation is used in an expression and
units or variables named "e or "E" exist.


[subsection "Unit Specifier"]








|











|
>
>
>
|
|







32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
Operands can be numbers or numbers with units, sometimes saved in
variables or a units dictionary. [package uexpr] does not operate on
strings.  Comparisons in [package uexpr] expressions return a numeric
1.0 for true, or 0.0 for false.

[subsection Numbers]

Numbers start with a decimal point or digit, and may include only one decimal point.
[example "1 1.23 .123"]
They may include a 10's exponent after an e or E. The exponent may be signed.
[example "1e-3 == 0.001"]
Numbers can start after most operators or a space.
[package uexpr] treats all numbers as double precision reals.

When a string can be interpreted as a number it will be.

[example "3e+5e == 300000*e"]
The first "e" is part of a number. The second e is interpreted as a variable named e. A number followed by a variable with no operator in between implies a multiplication.

[example "3e +5e == 3*e+5*e"]

[example "3E+ 5e == 3*E+5*e"]

Here the space breaks up the scientific number definition, so both "e"
and "E" are interpreted as variables or units.

Keep this in mind if scientific notation is used in an expression and
units or variables named "e or "E" exist.


[subsection "Unit Specifier"]

80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120








121
122
123
124
125
126

127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
















164
165
166
167
168
169
170
Values with units are simple expressions containing a pure number
followed by (or multiplied by) a unit specifier. They are returned
from [cmd uexpr], and can be saved in variables referenced by other
[cmd uexpr] expressions.

[example "100*BTU/hr ft^2"]

defines a heat flux rate.

[para]
Internally the value with units is a list containing a magnitude and
exponents on the basis units. For example using US Customary basis units: lbm, ft, sec, ...

[example 3ft/sec]
gives a list starting with 3 0 1 -1 ...
[example 3m/sec]
gives a list starting with 9.8425.. 0 1 -1 ...
Note both values are speeds, and both have the same exponents on their basis units.

Compatible units have identical exponents on the basis units, and can
be added, subtracted and compared.

[subsection "Named Values"]

Named values can be variables from the context of the call to uexpr,
or units or constants from the units dictionary. Names must start with
a "_" or alphabetic character, and can contain "_" "." and
alphanumeric characters. If the variable is processed by [cmd uexpr], it
will be treated as a subexpression, and its result inserted into the
expression being evaluated. This allows variables to hold values with
units, which are returned from [cmd uexpr] as simple expressions, not pure
numbers.

[subsection Variables]

If a name follows a "$" it is a reference to a variable in the context
of the uexpr call or a local variable in a function definition. If a
name does not follow a "$" it may still be a variable (see NAME
RESOLUTION). If a variable name follows "$" the whole expression should be
braced {}, to prevent Tcl from bypassing the variable handling by
[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.

[example 3ft+1inch]







|








|




















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





|
>
|



















<
<
<
<
<
<
<
<
<
<
<



|


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







83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158











159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
Values with units are simple expressions containing a pure number
followed by (or multiplied by) a unit specifier. They are returned
from [cmd uexpr], and can be saved in variables referenced by other
[cmd uexpr] expressions.

[example "100*BTU/hr ft^2"]

defines a heat flux.

[para]
Internally the value with units is a list containing a magnitude and
exponents on the basis units. For example using US Customary basis units: lbm, ft, sec, ...

[example 3ft/sec]
gives a list starting with 3 0 1 -1 ...
[example 3m/sec]
gives a list starting with 9.8425... 0 1 -1 ...
Note both values are speeds, and both have the same exponents on their basis units.

Compatible units have identical exponents on the basis units, and can
be added, subtracted and compared.

[subsection "Named Values"]

Named values can be variables from the context of the call to uexpr,
or units or constants from the units dictionary. Names must start with
a "_" or alphabetic character, and can contain "_" "." and
alphanumeric characters. If the variable is processed by [cmd uexpr], it
will be treated as a subexpression, and its result inserted into the
expression being evaluated. This allows variables to hold values with
units, which are returned from [cmd uexpr] as simple expressions, not pure
numbers.

[subsection Variables]

If a name follows a "$" it is a reference to a variable in the context
of the uexpr call or a local variable in a function definition. If a
name does not follow a "$" it may still be a variable
(see [sectref "Name Resolution"]). If a variable name follows "$" the whole
expression should be braced {}, to prevent Tcl from bypassing the
variable handling by [cmd uexpr].

[para]
Unlike the rest of Tcl, variable names referenced using "$" can also
include "_" and "." characters. See [sectref "Name Resolution"].

[para] Tcl array members (such as a(b3) can only be read using "$". A
reference without the "$" prefix will be interpreted as a function
call, or an implied multiply of a variable by a subexpression.

[subsection "Units and Constants"]

Units and constants are defined in a units dictionary, common to all
uexpr calls in an interpreter.  If a name follows a single ":" it will
only be looked up in the units dictionary.  If a unit or constant name
does not follow ":", it may be interpreted as a reference to a
variable, see [sectref "Name Resolution"] below.

[example ":ft"]

look up the unit named ft in the units dictionary


[subsection "Name Resolution"]


If a name does not follow a "$" or ":" then the first value available
from the following list is used:

[list_begin itemized]

[item] If the name is in an expression defining a function (see [cmd uexpr::func])
it is looked up in the function's parameter list

[item] The name is looked up as a variable in the context of the uexpr call.












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

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

[list_end]

If the name follows a "$" it is not looked up in the units
dictionary. A name following a "$" can be a Tcl array
member reference.

[example {uexpr {3*$a(b)+1}}]

will multiply the value of the Tcl array "a" member "b" by 3 then add 1. Very similar to what Tcl [cmd expr] would do.

Without the "$" uexpr will interpret (b) as a subexpression or possibly part of a function call.

[example {uexpr {3*a(b)+1}}]

where a is a variable (and not a function name) multiplies 3, a and b, then adds 1



[subsection "Named Values and Implied Multiply"]

A number immediately followed by a named value will be interpreted as
if there was an implied multiply between them.

[example 3ft+1inch]
187
188
189
190
191
192
193
194
195
196
197
198



199
200
201
202
203
204


205

206
207
208
209
210
211
212
213
214
215
216
217




218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251

252


253




254







255
256
257
258
259

260
261
262
263
264
265
266
267
268
269
[package uexpr] does not deal with integers and bitwise operators (& |
~ << >>), or boolean operators (! && ||) or strings (eq ne lt le ge
gt), or list searches (in ni).
Operators roughly in descending order of precedence:

[list_begin itemized]

[item] Variable..(index expression list) index into a list or array in a variable.
[example "set v [lb]= 1,2,3,4[rb]"]
saves a list in the variable v
[example "= v..2"]
returns the 3rd list entry (3 in this case)



[example "set m [lb]= (1,2,3),(11,22,33),(111,222,333)[rb]"]
saves a 3x3 array in m
[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
multiplication. However their effective precedence is the maximum of
their precedence and the precedence of the operator to their left (if
any). This means:

[example "-2^2 == 0-2^2 == -4"]

This is not true for [cmd expr], however it appears to match the conventions in
"Handbook of Mathematical Functions" by Abramowitz and Stegun. See the
definition of the error function for an example.

[item] + - ++ -- addition and subtraction, left and right arguments
must have compatible units. ++ and -- will also insert a line break in
LaTeX formatted output.

[item] < <= =< == != <> .= => > comparisons, return 1.0 for true,
0.0 for false. note ==, != and <> are not that useful with real
numbers. Left and right arguments must have compatible units.
[para]
Comparisons can be chained. The result is true if all comparisons are true.

[example 7<a<13]

will return true (1.0) if the value of a is between 7 and 13


[item] "," separate items in a list


[item] "=" is a very low precedence divide right now, however


the normal processing of expressions keeps "=" from being




interpreted as part of an expression.








[list_end]

"( )" group sub expressions (as for [cmd expr]).


"%" is a constant defined as 0.01 in the units dict.

[example "45%*a = 045*a"]


[section "Math Functions"]

Most math functions that work on real numbers in [cmd expr] are available in [package uexpr]. However they are now unit aware.

[list_begin itemized]







|
|



>
>
>

|




>
>
|
>











|
>
>
>
>

|

|









|







|










|

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



|

>


|







204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
[package uexpr] does not deal with integers and bitwise operators (& |
~ << >>), or boolean operators (! && ||) or strings (eq ne lt le ge
gt), or list searches (in ni).
Operators roughly in descending order of precedence:

[list_begin itemized]

[item] .. index into a list or nested lists in a variable.
[example "set v {1 2 3 4}"]
saves a list in the variable v
[example "= v..2"]
returns the 3rd list entry (3 in this case)
note the list could have been generated using the [cmd uexpr] "," operator too:
[example "set v [lb]= 1,2,3,4[rb]"]
To index into nested lists use a list of index values:
[example "set m [lb]= (1,2,3),(11,22,33),(111,222,333)[rb]"]
saves a 3x3 nested list in m
[example "=m..(2,0)"]
returns a value of 111.

[para]

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


[item] ** ^ exponentiation. The right argument (exponent) must be
unitless. The left argument can have units. However if the exponent is
not an integer, and the left operand has units, the result basis unit
exponents may not be integers. In some cases this can cause
problems. see [sectref "Fractional or Non-Integer Exponents"].  Note
exponentiations group to the right so:

[example "2^3^2 == 2^(3^2) == 512"]

[item] "implied multiply" multiplication is implied any time two
values are not separated by an operator. A space between values is
common, however a number followed by a named value also implies
multiplication. Implied multiply has a higher precedence than multiply
or divide. Useful in unit specifiers.

[item] / divide, has same precedence as multiply.

[item] * multiply, has same precedence as divide.

[item] Unary + - operators to the left of their operand
have a minimum precedence between addition or subtraction and
multiplication. However their effective precedence is the maximum of
their precedence and the precedence of the operator to their left (if
any). This means:

[example "-2^2 == 0-2^2 == -4"]

This is not true for Tcl [cmd expr], however it appears to match the conventions in
"Handbook of Mathematical Functions" by Abramowitz and Stegun. See the
definition of the error function for an example.

[item] + - ++ -- addition and subtraction, left and right arguments
must have compatible units. ++ and -- will also insert a line break in
LaTeX formatted output.

[item] < <= =< == != <> >= => > comparisons, return 1.0 for true,
0.0 for false. note ==, != and <> are not that useful with real
numbers. Left and right arguments must have compatible units.
[para]
Comparisons can be chained. The result is true if all comparisons are true.

[example 7<a<13]

will return true (1.0) if the value of a is between 7 and 13


[item] "," separate items in a list. Used to build lists and nested lists, such as lists of function arguments:

[example hypot(a,b)]

Also can be used to generate lists to use with the .. operator. For
example coefficients for a polynomial using the sum function:

[example {set C [uexpr 1,2,3,4]}]
[example "set x 0.1"]
[example "uexpr sum(i,0,3,C..i*x^i)"]
will return 1.234


[item] "=" marks a result unit specifier for an expression. For example:
[example {uexpr 15 lbf/3 in^2=psi}]
would return "5.0 psi"
Multiple result specifiers can be given:
[example {uexpr::ltxExpr {15 N / 5 cm^2 = bar = Pa}}]
would return results in bar and pascals.

[list_end]

"( )" group subexpressions (as for [cmd expr]).

[para]
"%" is a constant defined as 0.01 in the units dict.

[example "45%*a = 0.45*a"]


[section "Math Functions"]

Most math functions that work on real numbers in [cmd expr] are available in [package uexpr]. However they are now unit aware.

[list_begin itemized]
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
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
will be left with its last value

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

will return 31.0

[para]
A polynomial with coefficients in c

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

will return 54321.0

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

[example hasUnits(3)]

returns 0

369
370
371
372
373
374
375
376
377
378
379
380
381
382
383

If the expression ends with "=" followed by a unit specifier, the
result is formatted to use those units. If the units specified do not
match the actual result units, they will be multiplied or divided by
enough base units to make them match. If multiple result unit
specifiers are given only the last is used. If no result units are specified,
the resulting unit expression will contain only base units. In any
case the result can be used as an input to uexpr.  [para]

[para]
[cmd [lb]uexpr[rb]] functions similarly to [cmd expr] with several
significant differences. It is not a replacement for [cmd expr].

For example:








|







411
412
413
414
415
416
417
418
419
420
421
422
423
424
425

If the expression ends with "=" followed by a unit specifier, the
result is formatted to use those units. If the units specified do not
match the actual result units, they will be multiplied or divided by
enough base units to make them match. If multiple result unit
specifiers are given only the last is used. If no result units are specified,
the resulting unit expression will contain only base units. In any
case the result can be used in an expression evaluated by uexpr.  [para]

[para]
[cmd [lb]uexpr[rb]] functions similarly to [cmd expr] with several
significant differences. It is not a replacement for [cmd expr].

For example:

427
428
429
430
431
432
433










434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
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
as:

[example "uexpr::newUnit mile 5280*ft"]

Note, units are very similar to variables, except: Variables are only
looked up in the calling context of the expression evaluator. Unit
definitions are global, available to any instance of uexpr in the same
Tcl interpreter.




[call [cmd uexpr::func] [arg name] [arg Parameters] [arg expression]]

[cmd uexpr::func] adds a new function, usable in any uexpr call in the
same interpreter. Variables in the expression that are not in the
parameter list, have the value of the variable in the calling context
at the time the function is defined. Variables in the parameter list
have values set at the time the function is used. If not all the
parameters are supplied in the function call, the value of a variable







>
>
>
>
>
>
>
>
>
>



|

|





|







|







469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
return a list of all units matching the glob pattern.
Return all unit names if no pattern is given.

[call [cmd uexpr::unitsLike] [opt [arg expression]]] Returns a sorted
list of all units and constants with the same dimensions (compatible
units) as the result of the given expression. If no expression is
given, all unitless constants are returned.

[para]
For example if no new units are defined:
[example {uexpr::unitsLike psi}]
returns: atm bar Pa pascal psi Torr

[para]
[example {uexpr::unitsLike BTU/hr}]
returns: hp watt


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

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

[example "uexpr::newUnit mile 5280*ft"]

Note, units are very similar to variables, except: variables are
looked up in the calling context of the expression evaluator. Unit
definitions are global, available to any instance of uexpr in the same
Tcl interpreter.




[call [cmd uexpr::func] [arg name] [arg "parameter_names"] [arg expression]]

[cmd uexpr::func] adds a new function, usable in any uexpr call in the
same interpreter. Variables in the expression that are not in the
parameter list, have the value of the variable in the calling context
at the time the function is defined. Variables in the parameter list
have values set at the time the function is used. If not all the
parameters are supplied in the function call, the value of a variable
492
493
494
495
496
497
498







499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527

[example_begin]
set c 33
uexpr ss(4,5)
[example_end]
returns 12 (4+5+3), the change in c is ignored as it was not a function parameter









[list_end]


[section "Differences between [cmd uexpr] and [cmd expr]"]

[cmd uexpr] is not a replacement for [cmd expr].

[list_begin itemized]

[item] numbers are all double precision. Mostly because many unit
conversion factors are not integers.


[item] Variable names without "$" or ":" prefixes can be looked up in
the calling context or the units array or both:

[item] variables used in expressions can contain complete expressions,
not just numbers. The variable contents will be evaluated and its
value used in the expression. This will likely not work if Tcl gets to the
variable first. Any time an expression refers to variables with the
"$" prefix the expression should be within braces.

[item] a variable can contain a list or nested lists of unit
expressions. To get a value from a list, or nested list use the ".."
operator.

[item] There are no bit or logical
operations (<< >> & | && ||).







>
>
>
>
>
>
>














|
|




|
|







544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586

[example_begin]
set c 33
uexpr ss(4,5)
[example_end]
returns 12 (4+5+3), the change in c is ignored as it was not a function parameter


[call [cmd uexpr::uset] [arg name] [arg "expression"]]

[cmd uexpr::uset] writes the result of evaluating an expression to a
variable, tcl array member or into a list or nested list saved in a
variable.


[list_end]


[section "Differences between [cmd uexpr] and [cmd expr]"]

[cmd uexpr] is not a replacement for [cmd expr].

[list_begin itemized]

[item] numbers are all double precision. Mostly because many unit
conversion factors are not integers.


[item] Variable names without "$" or ":" prefixes will be looked up in
the calling context or the units dictionary.

[item] variables used in expressions can contain complete expressions,
not just numbers. The variable contents will be evaluated and its
value used in the expression. This will likely not work if Tcl gets to the
variable first. Any expression that refers to variables with the
"$" prefix should be within braces.

[item] a variable can contain a list or nested lists of unit
expressions. To get a value from a list, or nested list use the ".."
operator.

[item] There are no bit or logical
operations (<< >> & | && ||).
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
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 (/).








|
|







594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
Or using ltxExpr, to get the results in cm and feet:

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

[item] There are no type conversion functions (double entier,
wide). int() exists, and returns a double with an integer value.

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

[item] When two values are not separated by an operator there is an
implied multiply. Since variable names cannot start with a number or
decimal point, "3a" will be interpreted as "3 a" or "5ft" as "5 ft".
Implied multiplies have higher precedence than normal
multiply (*) or divide (/).

619
620
621
622
623
624
625
626

627

628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654

[example "[cmd uexpr] 20*degC+zdc-zdf=degF"]

Similar logic holds for gauge, absolute and differential
pressures. The offset from absolute to gauge pressure is atm (one
atmosphere).



[subsection "Customary and Self Consistent Units"]


An expression and its result have self consistent units if the result
magnitude does not change when units are removed from all values in
the expression (as would happen if evaluated with [cmd expr]). For
example the volume of a rectangular solid:

[example v=h*w*l]

if h, w, l are given in inches, and the volume is given in cubic
inches (in^3) the units are self consistent. Given this self
consistent formula, the [package uexpr] package can calculate the
volume in any desired units as long as h, w and l are all given in
length units.

[para]

Many common formulas use customary units. To use them with this
package they need to be written in self consistent form. One simple
way to do this is divide all variables in the expression by the units
specified. Multiply the expression by the specified result units.  For
example a formula to calculate liquid flow through a valve for simple cases (no
cavitation or flashing, low viscosity) is:

[example "Q = Cv*sqrt(DP/Sg)"]

where









>

>



















|







678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715

[example "[cmd uexpr] 20*degC+zdc-zdf=degF"]

Similar logic holds for gauge, absolute and differential
pressures. The offset from absolute to gauge pressure is atm (one
atmosphere).



[subsection "Customary and Self Consistent Units"]


An expression and its result have self consistent units if the result
magnitude does not change when units are removed from all values in
the expression (as would happen if evaluated with [cmd expr]). For
example the volume of a rectangular solid:

[example v=h*w*l]

if h, w, l are given in inches, and the volume is given in cubic
inches (in^3) the units are self consistent. Given this self
consistent formula, the [package uexpr] package can calculate the
volume in any desired units as long as h, w and l are all given in
length units.

[para]

Many common formulas use customary units. To use them with this
package they need to be written in self consistent form. One simple
way to do this is divide all variables in the expression by the units
specified, then multiply the expression by the specified result units.  For
example a formula to calculate liquid flow through a valve for simple cases (no
cavitation or flashing, low viscosity) is:

[example "Q = Cv*sqrt(DP/Sg)"]

where

684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
compressibility of gases, use 1.0 for liquids or gases when the
operating pressure is much higher that the pressure drop.

[example "w = 0.09970190*C.d*Y*d^2*sqrt((DP*den)/(1-(d/D)^4))"]

The self consistent version of this formula is:

[example "w = sqrt(2)*Cd*Y*pi/4*d^2*sqrt((DP*den)/(1-(d/D)^4))"]

the only vaguely mysterious number in the self consistent formula is sqrt(2).
It will work with inputs and results in any compatible units. Meter inlet and
throat size in any combination of ft, in, cm... DP could be given as
psi, Pascal, bar, mm mercury ... Density (den) could be given as
lbm/ft^3, gram/cm^3 ... The resulting mass flow rate can be lbm/hr
gram/sec ...

[para] Often a formula for SI units will be self consistent, mass Kg,
lengths meter, time sec, force newton (N), pressure Pascal (Pa)
.... But it needs to be checked.


[subsection "Fractional or Non-Integer Exponents"]

In practical formulas, fractional exponents on values with units
rarely occur. Although mathematicians and physicists keep trying.








|










|







745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
compressibility of gases, use 1.0 for liquids or gases when the
operating pressure is much higher that the pressure drop.

[example "w = 0.09970190*C.d*Y*d^2*sqrt((DP*den)/(1-(d/D)^4))"]

The self consistent version of this formula is:

[example "w = sqrt(2)*C.d*Y*pi/4*d^2*sqrt((DP*den)/(1-(d/D)^4))"]

the only vaguely mysterious number in the self consistent formula is sqrt(2).
It will work with inputs and results in any compatible units. Meter inlet and
throat size in any combination of ft, in, cm... DP could be given as
psi, Pascal, bar, mm mercury ... Density (den) could be given as
lbm/ft^3, gram/cm^3 ... The resulting mass flow rate can be lbm/hr
gram/sec ...

[para] Often a formula for SI units will be self consistent, mass Kg,
lengths meter, time sec, force newton (N), pressure Pascal (Pa)
... But it needs to be checked.


[subsection "Fractional or Non-Integer Exponents"]

In practical formulas, fractional exponents on values with units
rarely occur. Although mathematicians and physicists keep trying.

Changes to uexpr.html.

89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
	margin-bottom: 	1em;
	border-bottom:	1px solid black;
    }
--></style>
</head>
<!-- Generated from file 'uexpr.dt' by tcllib/doctools with format 'html'
   -->
<!-- Copyright &amp;copy; 2021 J.D Bruchie (BSD License)
   -->
<!-- uexpr.n
   -->
<body><div class="doctools">
<h1 class="doctools_title">uexpr(n) 0.1 uexpr &quot;Calculations with Units&quot;</h1>
<div id="name" class="doctools_section"><h2><a name="name">Name</a></h2>
<p>uexpr - A Tcl package to evaluate and format expressions that may include values with units</p>







|







89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
	margin-bottom: 	1em;
	border-bottom:	1px solid black;
    }
--></style>
</head>
<!-- Generated from file 'uexpr.dt' by tcllib/doctools with format 'html'
   -->
<!-- Copyright &amp;copy; 2021-2024 J.D Bruchie (BSD License)
   -->
<!-- uexpr.n
   -->
<body><div class="doctools">
<h1 class="doctools_title">uexpr(n) 0.1 uexpr &quot;Calculations with Units&quot;</h1>
<div id="name" class="doctools_section"><h2><a name="name">Name</a></h2>
<p>uexpr - A Tcl package to evaluate and format expressions that may include values with units</p>
144
145
146
147
148
149
150
151

152
153
154
155
156
157
158
</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>
<div id="section1" class="doctools_section"><h2><a name="section1">Description</a></h2>
<p>Package <b class="package">uexpr</b> provides commands to evaluate and format
expressions that may include values with units. Its intended to be
used as part of a program to document engineering, physical and







|
>







144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
</ul>
<ul class="doctools_syntax">
<li><a href="#1"><b class="cmd">uexpr</b> <i class="arg">expression</i> <span class="opt">?<i class="arg">expressions</i>?</span> ...</a></li>
<li><a href="#2"><b class="cmd">::uexpr::ltxExpr</b> <i class="arg">expression</i> <span class="opt">?<i class="arg">expression</i>?</span> ...</a></li>
<li><a href="#3"><b class="cmd">uexpr::unitNames</b> <span class="opt">?<i class="arg">glob pattern</i>?</span></a></li>
<li><a href="#4"><b class="cmd">uexpr::unitsLike</b> <span class="opt">?<i class="arg">expression</i>?</span></a></li>
<li><a href="#5"><b class="cmd">uexpr::newUnit</b> <i class="arg">name</i> <i class="arg">expression</i></a></li>
<li><a href="#6"><b class="cmd">uexpr::func</b> <i class="arg">name</i> <i class="arg">parameter_names</i> <i class="arg">expression</i></a></li>
<li><a href="#7"><b class="cmd">uexpr::uset</b> <i class="arg">name</i> <i class="arg">expression</i></a></li>
</ul>
</div>
</div>
<div id="section1" class="doctools_section"><h2><a name="section1">Description</a></h2>
<p>Package <b class="package">uexpr</b> provides commands to evaluate and format
expressions that may include values with units. Its intended to be
used as part of a program to document engineering, physical and
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
</div>
<div id="section2" class="doctools_section"><h2><a name="section2">Operands</a></h2>
<p>Operands can be numbers or numbers with units, sometimes saved in
variables or a units dictionary. <b class="package">uexpr</b> does not operate on
strings.  Comparisons in <b class="package">uexpr</b> expressions return a numeric
1.0 for true, or 0.0 for false.</p>
<div id="subsection1" class="doctools_subsection"><h3><a name="subsection1">Numbers</a></h3>
<p>Numbers start with a decimal point or digit, and may include only one decimal point..</p>
<pre class="doctools_example">1 1.23 .123</pre>
<p>They may include a 10's exponent after an e or E. The exponent may be signed.</p>
<pre class="doctools_example">1e-3 == 0.001</pre>
<p>Numbers can start after most operators or a space.
<b class="package">uexpr</b> treats all numbers as double precision reals.
When a string can be interpreted as a number it will be.</p>
<pre class="doctools_example">3e+5e == 300000*e</pre>
<p>The first &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>







|










<
|
|







172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189

190
191
192
193
194
195
196
197
198
</div>
<div id="section2" class="doctools_section"><h2><a name="section2">Operands</a></h2>
<p>Operands can be numbers or numbers with units, sometimes saved in
variables or a units dictionary. <b class="package">uexpr</b> does not operate on
strings.  Comparisons in <b class="package">uexpr</b> expressions return a numeric
1.0 for true, or 0.0 for false.</p>
<div id="subsection1" class="doctools_subsection"><h3><a name="subsection1">Numbers</a></h3>
<p>Numbers start with a decimal point or digit, and may include only one decimal point.</p>
<pre class="doctools_example">1 1.23 .123</pre>
<p>They may include a 10's exponent after an e or E. The exponent may be signed.</p>
<pre class="doctools_example">1e-3 == 0.001</pre>
<p>Numbers can start after most operators or a space.
<b class="package">uexpr</b> treats all numbers as double precision reals.
When a string can be interpreted as a number it will be.</p>
<pre class="doctools_example">3e+5e == 300000*e</pre>
<p>The first &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>
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241





242
243
244
245
246

247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270








271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294



295
296
297
298

299

300
301
302
303
304
305
306
307
308




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







330

331


332
333
334
335
336
337
338
339
340
341
342
343
</div>
<div id="subsection3" class="doctools_subsection"><h3><a name="subsection3">Values with Units</a></h3>
<p>Values with units are simple expressions containing a pure number
followed by (or multiplied by) a unit specifier. They are returned
from <b class="cmd">uexpr</b>, and can be saved in variables referenced by other
<b class="cmd">uexpr</b> expressions.</p>
<pre class="doctools_example">100*BTU/hr ft^2</pre>
<p>defines a heat flux rate.</p>
<p>Internally the value with units is a list containing a magnitude and
exponents on the basis units. For example using US Customary basis units: lbm, ft, sec, ...</p>
<pre class="doctools_example">3ft/sec</pre>
<p>gives a list starting with 3 0 1 -1 ...</p>
<pre class="doctools_example">3m/sec</pre>
<p>gives a list starting with 9.8425.. 0 1 -1 ...
Note both values are speeds, and both have the same exponents on their basis units.
Compatible units have identical exponents on the basis units, and can
be added, subtracted and compared.</p>
</div>
<div id="subsection4" class="doctools_subsection"><h3><a name="subsection4">Named Values</a></h3>
<p>Named values can be variables from the context of the call to uexpr,
or units or constants from the units dictionary. Names must start with
a &quot;_&quot; or alphabetic character, and can contain &quot;_&quot; &quot;.&quot; and
alphanumeric characters. If the variable is processed by <b class="cmd">uexpr</b>, it
will be treated as a subexpression, and its result inserted into the
expression being evaluated. This allows variables to hold values with
units, which are returned from <b class="cmd">uexpr</b> as simple expressions, not pure
numbers.</p>
</div>
<div id="subsection5" class="doctools_subsection"><h3><a name="subsection5">Variables</a></h3>
<p>If a name follows a &quot;$&quot; it is a reference to a variable in the context
of the uexpr call or a local variable in a function definition. If a
name does not follow a &quot;$&quot; it may still be a variable (see NAME
RESOLUTION). If a variable name follows &quot;$&quot; the whole expression should be
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
precedence than divide so:</p>
<pre class="doctools_example">diam/3ft</pre>
<p>or</p>
<pre class="doctools_example">diam/3 ft</pre>
<p>will be interpreted as diam/(3*ft).</p>
</div>
</div>
<div id="section3" class="doctools_section"><h2><a name="section3">Operators</a></h2>
<p><b class="package">uexpr</b> does not deal with integers and bitwise operators (&amp; |
~ &lt;&lt; &gt;&gt;), or boolean operators (! &amp;&amp; ||) or strings (eq ne lt le ge
gt), or list searches (in ni).
Operators roughly in descending order of precedence:</p>
<ul class="doctools_itemized">
<li><p>Variable..(index expression list) index into a list or array in a variable.</p>
<pre class="doctools_example">set v [= 1,2,3,4]</pre>
<p>saves a list in the variable v</p>
<pre class="doctools_example">= v..2</pre>
<p>returns the 3rd list entry (3 in this case)</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







|





|

















|
|
|
|
>
>
>
>
>




|
>
|










<
<
<
<
<
<
<
<
<


|

>
>
>
>
>
>
>
>



















|
|


|
>
>
>

|


>
|
>








|
>
>
>
>
|
|






|





|





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

|
|
|







207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263









264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
</div>
<div id="subsection3" class="doctools_subsection"><h3><a name="subsection3">Values with Units</a></h3>
<p>Values with units are simple expressions containing a pure number
followed by (or multiplied by) a unit specifier. They are returned
from <b class="cmd">uexpr</b>, and can be saved in variables referenced by other
<b class="cmd">uexpr</b> expressions.</p>
<pre class="doctools_example">100*BTU/hr ft^2</pre>
<p>defines a heat flux.</p>
<p>Internally the value with units is a list containing a magnitude and
exponents on the basis units. For example using US Customary basis units: lbm, ft, sec, ...</p>
<pre class="doctools_example">3ft/sec</pre>
<p>gives a list starting with 3 0 1 -1 ...</p>
<pre class="doctools_example">3m/sec</pre>
<p>gives a list starting with 9.8425... 0 1 -1 ...
Note both values are speeds, and both have the same exponents on their basis units.
Compatible units have identical exponents on the basis units, and can
be added, subtracted and compared.</p>
</div>
<div id="subsection4" class="doctools_subsection"><h3><a name="subsection4">Named Values</a></h3>
<p>Named values can be variables from the context of the call to uexpr,
or units or constants from the units dictionary. Names must start with
a &quot;_&quot; or alphabetic character, and can contain &quot;_&quot; &quot;.&quot; and
alphanumeric characters. If the variable is processed by <b class="cmd">uexpr</b>, it
will be treated as a subexpression, and its result inserted into the
expression being evaluated. This allows variables to hold values with
units, which are returned from <b class="cmd">uexpr</b> as simple expressions, not pure
numbers.</p>
</div>
<div id="subsection5" class="doctools_subsection"><h3><a name="subsection5">Variables</a></h3>
<p>If a name follows a &quot;$&quot; it is a reference to a variable in the context
of the uexpr call or a local variable in a function definition. If a
name does not follow a &quot;$&quot; it may still be a variable
(see <span class="sectref"><a href="#subsection7">Name Resolution</a></span>). If a variable name follows &quot;$&quot; the whole
expression should be braced {}, to prevent Tcl from bypassing the
variable handling by <b class="cmd">uexpr</b>.</p>
<p>Unlike the rest of Tcl, variable names referenced using &quot;$&quot; can also
include &quot;_&quot; and &quot;.&quot; characters. See <span class="sectref"><a href="#subsection7">Name Resolution</a></span>.</p>
<p>Tcl array members (such as a(b3) can only be read using &quot;$&quot;. A
reference without the &quot;$&quot; prefix will be interpreted as a function
call, or an implied multiply of a variable by a subexpression.</p>
</div>
<div id="subsection6" class="doctools_subsection"><h3><a name="subsection6">Units and Constants</a></h3>
<p>Units and constants are defined in a units dictionary, common to all
uexpr calls in an interpreter.  If a name follows a single &quot;:&quot; it will
only be looked up in the units dictionary.  If a unit or constant name
does not follow &quot;:&quot;, it may be interpreted as a reference to a
variable, see <span class="sectref"><a href="#subsection7">Name Resolution</a></span> below.</p>
<pre class="doctools_example">:ft</pre>
<p>look up the unit named ft in the units dictionary</p>
</div>
<div id="subsection7" class="doctools_subsection"><h3><a name="subsection7">Name Resolution</a></h3>
<p>If a name does not follow a &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>The name is looked up in the units
dictionary.</p></li>
<li><p>An error occurs if no value was found.</p></li>
</ul>
<p>If the name follows a &quot;$&quot; it is not looked up in the units
dictionary. A name following a &quot;$&quot; can be a Tcl array
member reference.</p>
<pre class="doctools_example">uexpr {3*$a(b)+1}</pre>
<p>will multiply the value of the Tcl array &quot;a&quot; member &quot;b&quot; by 3 then add 1. Very similar to what Tcl <b class="cmd">expr</b> would do.
Without the &quot;$&quot; uexpr will interpret (b) as a subexpression or possibly part of a function call.</p>
<pre class="doctools_example">uexpr {3*a(b)+1}</pre>
<p>where a is a variable (and not a function name) multiplies 3, a and b, then adds 1</p>
</div>
<div id="subsection8" class="doctools_subsection"><h3><a name="subsection8">Named Values and Implied Multiply</a></h3>
<p>A number immediately followed by a named value will be interpreted as
if there was an implied multiply between them.</p>
<pre class="doctools_example">3ft+1inch</pre>
<p>will be interpreted as 3*ft+1*inch. Note implied multiply has a higher
precedence than divide so:</p>
<pre class="doctools_example">diam/3ft</pre>
<p>or</p>
<pre class="doctools_example">diam/3 ft</pre>
<p>will be interpreted as diam/(3*ft).</p>
</div>
</div>
<div id="section3" class="doctools_section"><h2><a name="section3">Operators</a></h2>
<p><b class="package">uexpr</b> does not deal with integers and bitwise operators (&amp; |
~ &lt;&lt; &gt;&gt;), or boolean operators (! &amp;&amp; ||) or strings (eq ne lt le ge
gt), or list searches (in ni).
Operators roughly in descending order of precedence:</p>
<ul class="doctools_itemized">
<li><p>.. index into a list or nested lists in a variable.</p>
<pre class="doctools_example">set v {1 2 3 4}</pre>
<p>saves a list in the variable v</p>
<pre class="doctools_example">= v..2</pre>
<p>returns the 3rd list entry (3 in this case)
note the list could have been generated using the <b class="cmd">uexpr</b> &quot;,&quot; operator too:</p>
<pre class="doctools_example">set v [= 1,2,3,4]</pre>
<p>To index into nested lists use a list of index values:</p>
<pre class="doctools_example">set m [= (1,2,3),(11,22,33),(111,222,333)]</pre>
<p>saves a 3x3 nested list in m</p>
<pre class="doctools_example">=m..(2,0)</pre>
<p>returns a value of 111.</p>
<p>This is rather fragile. If the indexes do not match the actual
nested list structure, there may not be any error, and odd results will be
returned.</p></li>
<li><p>** ^ exponentiation. The right argument (exponent) must be
unitless. The left argument can have units. However if the exponent is
not an integer, and the left operand has units, the result basis unit
exponents may not be integers. In some cases this can cause
problems. see <span class="sectref"><a href="#subsection12">Fractional or Non-Integer Exponents</a></span>.  Note
exponentiations group to the right so:</p>
<pre class="doctools_example">2^3^2 == 2^(3^2) == 512</pre>
</li>
<li><p>&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. Implied multiply has a higher precedence than multiply
or divide. Useful in unit specifiers.</p></li>
<li><p>/ divide, has same precedence as multiply.</p></li>
<li><p>* multiply, has same precedence as divide.</p></li>
<li><p>Unary + - operators to the left of their operand
have a minimum precedence between addition or subtraction and
multiplication. However their effective precedence is the maximum of
their precedence and the precedence of the operator to their left (if
any). This means:</p>
<pre class="doctools_example">-2^2 == 0-2^2 == -4</pre>
<p>This is not true for Tcl <b class="cmd">expr</b>, however it appears to match the conventions in
&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; &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. Used to build lists and nested lists, such as lists of function arguments:</p>
<pre class="doctools_example">hypot(a,b)</pre>
<p>Also can be used to generate lists to use with the .. operator. For
example coefficients for a polynomial using the sum function:</p>
<pre class="doctools_example">set C [uexpr 1,2,3,4]</pre>
<pre class="doctools_example">set x 0.1</pre>
<pre class="doctools_example">uexpr sum(i,0,3,C..i*x^i)</pre>
<p>will return 1.234</p></li>
<li><p>&quot;=&quot; marks a result unit specifier for an expression. For example:</p>
<pre class="doctools_example">uexpr 15 lbf/3 in^2=psi</pre>
<p>would return &quot;5.0 psi&quot;
Multiple result specifiers can be given:</p>
<pre class="doctools_example">uexpr::ltxExpr {15 N / 5 cm^2 = bar = Pa}</pre>
<p>would return results in bar and pascals.</p></li>
</ul>
<p>&quot;( )&quot; group subexpressions (as for <b class="cmd">expr</b>).</p>
<p>&quot;%&quot; is a constant defined as 0.01 in the units dict.</p>
<pre class="doctools_example">45%*a = 0.45*a</pre>
</div>
<div id="section4" class="doctools_section"><h2><a name="section4">Math Functions</a></h2>
<p>Most math functions that work on real numbers in <b class="cmd">expr</b> are available in <b class="package">uexpr</b>. However they are now unit aware.</p>
<ul class="doctools_itemized">
<li><p>Trigonometric Functions (sin cos tan) expect a single angle argument (deg rad
...).and return a pure number.</p></li>
<li><p>Inverse Trigonometric Functions (asin acos atan) expect a pure
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
<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>







|
|
|

|







390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
<li><p><b class="function">sum(var,first_index,last_index,expression)</b> sums an
expression for a range of index variable values. The first and last
index values must be unitless. they will be rounded to the nearest
integer. The index variable will be created if it does not exist, and
will be left with its last value</p>
<pre class="doctools_example">sum(i,0,4,2^i)</pre>
<p>will return 31.0</p>
<p>A polynomial with coefficients in c</p>
<pre class="doctools_example">set c [= 1,2,3,4,5]</pre>
<pre class="doctools_example">set x 10</pre>
<pre class="doctools_example">= sum(i,0,4,c..i*x^i)</pre>
<p>will return 54321.0</p></li>
<li><p><b class="function">hasUnits(expression)</b> returns 0 if the value is a pure number.</p>
<pre class="doctools_example">hasUnits(3)</pre>
<p>returns 0</p></li>
<li><p><b class="function">units(expression)</b> returns the basis units of the result
of the expression, with a magnitude of 1. For example any value with
units of a pressure (psi, bar, Pascal ...) will return the same unit
expression:</p>
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
then evaluates the expression and returns one result.
If the expression ends with &quot;=&quot; followed by a unit specifier, the
result is formatted to use those units. If the units specified do not
match the actual result units, they will be multiplied or divided by
enough base units to make them match. If multiple result unit
specifiers are given only the last is used. If no result units are specified,
the resulting unit expression will contain only base units. In any
case the result can be used as an input to uexpr.</p>
<p><b class="cmd">[uexpr]</b> functions similarly to <b class="cmd">expr</b> with several
significant differences. It is not a replacement for <b class="cmd">expr</b>.
For example:</p>
<pre class="doctools_example">uexpr 4*in+5*cm=mm</pre>
<p>returns something close to &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>







|







425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
then evaluates the expression and returns one result.
If the expression ends with &quot;=&quot; followed by a unit specifier, the
result is formatted to use those units. If the units specified do not
match the actual result units, they will be multiplied or divided by
enough base units to make them match. If multiple result unit
specifiers are given only the last is used. If no result units are specified,
the resulting unit expression will contain only base units. In any
case the result can be used in an expression evaluated by uexpr.</p>
<p><b class="cmd">[uexpr]</b> functions similarly to <b class="cmd">expr</b> with several
significant differences. It is not a replacement for <b class="cmd">expr</b>.
For example:</p>
<pre class="doctools_example">uexpr 4*in+5*cm=mm</pre>
<p>returns something close to &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>
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
<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>
<p>Note, units are very similar to variables, except: Variables are only
looked up in the calling context of the expression evaluator. Unit
definitions are global, available to any instance of uexpr in the same
Tcl interpreter.</p></dd>
<dt><a name="6"><b class="cmd">uexpr::func</b> <i class="arg">name</i> <i class="arg">Parameters</i> <i class="arg">expression</i></a></dt>
<dd><p><b class="cmd">uexpr::func</b> adds a new function, usable in any uexpr call in the
same interpreter. Variables in the expression that are not in the
parameter list, have the value of the variable in the calling context
at the time the function is defined. Variables in the parameter list
have values set at the time the function is used. If not all the
parameters are supplied in the function call, the value of a variable
with the same name (in the calling context) is used (it came from







|
>
>
>
>
>

|

|



|



|







464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
<dt><a name="3"><b class="cmd">uexpr::unitNames</b> <span class="opt">?<i class="arg">glob pattern</i>?</span></a></dt>
<dd><p>return a list of all units matching the glob pattern.
Return all unit names if no pattern is given.</p></dd>
<dt><a name="4"><b class="cmd">uexpr::unitsLike</b> <span class="opt">?<i class="arg">expression</i>?</span></a></dt>
<dd><p>Returns a sorted
list of all units and constants with the same dimensions (compatible
units) as the result of the given expression. If no expression is
given, all unitless constants are returned.</p>
<p>For example if no new units are defined:</p>
<pre class="doctools_example">uexpr::unitsLike psi</pre>
<p>returns: atm bar Pa pascal psi Torr</p>
<pre class="doctools_example">uexpr::unitsLike BTU/hr</pre>
<p>returns: hp watt</p></dd>
<dt><a name="5"><b class="cmd">uexpr::newUnit</b> <i class="arg">name</i> <i class="arg">expression</i></a></dt>
<dd><p><b class="cmd">uexpr::newUnit</b> adds another unit to the unit dictionary. The
expression defines the new unit in terms of any existing base or defined
unit. The unit dictionary is common to all contexts within a Tcl
interpreter. These changes are global. For example a mile is defined
as:</p>
<pre class="doctools_example">uexpr::newUnit mile 5280*ft</pre>
<p>Note, units are very similar to variables, except: variables are
looked up in the calling context of the expression evaluator. Unit
definitions are global, available to any instance of uexpr in the same
Tcl interpreter.</p></dd>
<dt><a name="6"><b class="cmd">uexpr::func</b> <i class="arg">name</i> <i class="arg">parameter_names</i> <i class="arg">expression</i></a></dt>
<dd><p><b class="cmd">uexpr::func</b> adds a new function, usable in any uexpr call in the
same interpreter. Variables in the expression that are not in the
parameter list, have the value of the variable in the calling context
at the time the function is defined. Variables in the parameter list
have values set at the time the function is used. If not all the
parameters are supplied in the function call, the value of a variable
with the same name (in the calling context) is used (it came from
490
491
492
493
494
495
496




497
498
499
500
501
502
503
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
</pre>
<p>returns 29 (4+22+3), b is not given, so the current value is used</p>
<pre class="doctools_example">
set c 33
uexpr ss(4,5)
</pre>
<p>returns 12 (4+5+3), the change in c is ignored as it was not a function parameter</p></dd>




</dl>
</div>
<div id="section6" class="doctools_section"><h2><a name="section6">Differences between <b class="cmd">uexpr</b> and <b class="cmd">expr</b></a></h2>
<p><b class="cmd">uexpr</b> is not a replacement for <b class="cmd">expr</b>.</p>
<ul class="doctools_itemized">
<li><p>numbers are all double precision. Mostly because many unit
conversion factors are not integers.</p></li>
<li><p>Variable names without &quot;$&quot; or &quot;:&quot; prefixes can be looked up in
the calling context or the units array or both:</p></li>
<li><p>variables used in expressions can contain complete expressions,
not just numbers. The variable contents will be evaluated and its
value used in the expression. This will likely not work if Tcl gets to the
variable first. Any time an expression refers to variables with the
&quot;$&quot; prefix the expression should be within braces.</p></li>
<li><p>a variable can contain a list or nested lists of unit
expressions. To get a value from a list, or nested list use the &quot;..&quot;
operator.</p></li>
<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>







>
>
>
>







|
|



|
|














|
|







519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
</pre>
<p>returns 29 (4+22+3), b is not given, so the current value is used</p>
<pre class="doctools_example">
set c 33
uexpr ss(4,5)
</pre>
<p>returns 12 (4+5+3), the change in c is ignored as it was not a function parameter</p></dd>
<dt><a name="7"><b class="cmd">uexpr::uset</b> <i class="arg">name</i> <i class="arg">expression</i></a></dt>
<dd><p><b class="cmd">uexpr::uset</b> writes the result of evaluating an expression to a
variable, tcl array member or into a list or nested list saved in a
variable.</p></dd>
</dl>
</div>
<div id="section6" class="doctools_section"><h2><a name="section6">Differences between <b class="cmd">uexpr</b> and <b class="cmd">expr</b></a></h2>
<p><b class="cmd">uexpr</b> is not a replacement for <b class="cmd">expr</b>.</p>
<ul class="doctools_itemized">
<li><p>numbers are all double precision. Mostly because many unit
conversion factors are not integers.</p></li>
<li><p>Variable names without &quot;$&quot; or &quot;:&quot; prefixes will be looked up in
the calling context or the units dictionary.</p></li>
<li><p>variables used in expressions can contain complete expressions,
not just numbers. The variable contents will be evaluated and its
value used in the expression. This will likely not work if Tcl gets to the
variable first. Any expression that refers to variables with the
&quot;$&quot; prefix should be within braces.</p></li>
<li><p>a variable can contain a list or nested lists of unit
expressions. To get a value from a list, or nested list use the &quot;..&quot;
operator.</p></li>
<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 the
::uexpr::ufunc namespace.</p></li>
<li><p>When two values are not separated by an operator there is an
implied multiply. Since variable names cannot start with a number or
decimal point, &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>
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
inches (in^3) the units are self consistent. Given this self
consistent formula, the <b class="package">uexpr</b> package can calculate the
volume in any desired units as long as h, w and l are all given in
length units.</p>
<p>Many common formulas use customary units. To use them with this
package they need to be written in self consistent form. One simple
way to do this is divide all variables in the expression by the units
specified. Multiply the expression by the specified result units.  For
example a formula to calculate liquid flow through a valve for simple cases (no
cavitation or flashing, low viscosity) is:</p>
<pre class="doctools_example">Q = Cv*sqrt(DP/Sg)</pre>
<p>where</p>
<pre class="doctools_example">Q is volumetric flow rate in gpm</pre>
<pre class="doctools_example">DP is pressure drop across the valve in psi</pre>
<pre class="doctools_example">Sg is liquid specific gravity relative to water</pre>







|







624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
inches (in^3) the units are self consistent. Given this self
consistent formula, the <b class="package">uexpr</b> package can calculate the
volume in any desired units as long as h, w and l are all given in
length units.</p>
<p>Many common formulas use customary units. To use them with this
package they need to be written in self consistent form. One simple
way to do this is divide all variables in the expression by the units
specified, then multiply the expression by the specified result units.  For
example a formula to calculate liquid flow through a valve for simple cases (no
cavitation or flashing, low viscosity) is:</p>
<pre class="doctools_example">Q = Cv*sqrt(DP/Sg)</pre>
<p>where</p>
<pre class="doctools_example">Q is volumetric flow rate in gpm</pre>
<pre class="doctools_example">DP is pressure drop across the valve in psi</pre>
<pre class="doctools_example">Sg is liquid specific gravity relative to water</pre>
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
and throat (d) diameter in inches, and flow rate (w) in lbm/hr (lbm is
pounds mass, as opposed to lbf pounds force in <b class="package">uexpr</b>). Cd is
a meter factor normally near 1.0. Y is a correction for
compressibility of gases, use 1.0 for liquids or gases when the
operating pressure is much higher that the pressure drop.</p>
<pre class="doctools_example">w = 0.09970190*C.d*Y*d^2*sqrt((DP*den)/(1-(d/D)^4))</pre>
<p>The self consistent version of this formula is:</p>
<pre class="doctools_example">w = sqrt(2)*Cd*Y*pi/4*d^2*sqrt((DP*den)/(1-(d/D)^4))</pre>
<p>the only vaguely mysterious number in the self consistent formula is sqrt(2).
It will work with inputs and results in any compatible units. Meter inlet and
throat size in any combination of ft, in, cm... DP could be given as
psi, Pascal, bar, mm mercury ... Density (den) could be given as
lbm/ft^3, gram/cm^3 ... The resulting mass flow rate can be lbm/hr
gram/sec ...</p>
<p>Often a formula for SI units will be self consistent, mass Kg,
lengths meter, time sec, force newton (N), pressure Pascal (Pa)
.... But it needs to be checked.</p>
</div>
<div id="subsection12" class="doctools_subsection"><h3><a name="subsection12">Fractional or Non-Integer Exponents</a></h3>
<p>In practical formulas, fractional exponents on values with units
rarely occur. Although mathematicians and physicists keep trying.</p>
<p>The internal representation of a value with units is a list of double
precision real numbers. The first entry is a magnitude, the others are
exponents on the basis units. All the unit definitions (initially, and







|








|







653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
and throat (d) diameter in inches, and flow rate (w) in lbm/hr (lbm is
pounds mass, as opposed to lbf pounds force in <b class="package">uexpr</b>). Cd is
a meter factor normally near 1.0. Y is a correction for
compressibility of gases, use 1.0 for liquids or gases when the
operating pressure is much higher that the pressure drop.</p>
<pre class="doctools_example">w = 0.09970190*C.d*Y*d^2*sqrt((DP*den)/(1-(d/D)^4))</pre>
<p>The self consistent version of this formula is:</p>
<pre class="doctools_example">w = sqrt(2)*C.d*Y*pi/4*d^2*sqrt((DP*den)/(1-(d/D)^4))</pre>
<p>the only vaguely mysterious number in the self consistent formula is sqrt(2).
It will work with inputs and results in any compatible units. Meter inlet and
throat size in any combination of ft, in, cm... DP could be given as
psi, Pascal, bar, mm mercury ... Density (den) could be given as
lbm/ft^3, gram/cm^3 ... The resulting mass flow rate can be lbm/hr
gram/sec ...</p>
<p>Often a formula for SI units will be self consistent, mass Kg,
lengths meter, time sec, force newton (N), pressure Pascal (Pa)
... But it needs to be checked.</p>
</div>
<div id="subsection12" class="doctools_subsection"><h3><a name="subsection12">Fractional or Non-Integer Exponents</a></h3>
<p>In practical formulas, fractional exponents on values with units
rarely occur. Although mathematicians and physicists keep trying.</p>
<p>The internal representation of a value with units is a list of double
precision real numbers. The first entry is a magnitude, the others are
exponents on the basis units. All the unit definitions (initially, and
674
675
676
677
678
679
680
681
682
683
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>







|


707
708
709
710
711
712
713
714
715
716
is unitless, and there are no fractional exponents on the basis units.</p>
</div>
</div>
<div id="keywords" class="doctools_section"><h2><a name="keywords">Keywords</a></h2>
<p>Calculations, uexpr</p>
</div>
<div id="copyright" class="doctools_section"><h2><a name="copyright">Copyright</a></h2>
<p>Copyright &copy; 2021-2024 J.D Bruchie (BSD License)</p>
</div>
</div></body></html>

Changes to uexpr.n.

1
2
3
4
5
6
7
8
9
10
'\"
'\" Generated from file 'uexpr\&.dt' by tcllib/doctools with format 'nroff'
'\" Copyright (c) 2021 J\&.D Bruchie (BSD License)
'\"
.TH "uexpr" n 0\&.1 uexpr "Calculations with Units"
.\" The -*- nroff -*- definitions below are for supplemental macros used
.\" in Tcl/Tk manual entries.
.\"
.\" .AP type name in/out ?indent?
.\"	Start paragraph describing an argument to a library procedure.


|







1
2
3
4
5
6
7
8
9
10
'\"
'\" Generated from file 'uexpr\&.dt' by tcllib/doctools with format 'nroff'
'\" Copyright (c) 2021-2024 J\&.D Bruchie (BSD License)
'\"
.TH "uexpr" n 0\&.1 uexpr "Calculations with Units"
.\" The -*- nroff -*- definitions below are for supplemental macros used
.\" in Tcl/Tk manual entries.
.\"
.\" .AP type name in/out ?indent?
.\"	Start paragraph describing an argument to a library procedure.
286
287
288
289
290
291
292
293


294
295
296
297
298
299
300
.sp
\fBuexpr::unitNames\fR ?\fIglob pattern\fR?
.sp
\fBuexpr::unitsLike\fR ?\fIexpression\fR?
.sp
\fBuexpr::newUnit\fR \fIname\fR \fIexpression\fR
.sp
\fBuexpr::func\fR \fIname\fR \fIParameters\fR \fIexpression\fR


.sp
.BE
.SH DESCRIPTION
Package \fBuexpr\fR provides commands to evaluate and format
expressions that may include values with units\&. Its intended to be
used as part of a program to document engineering, physical and
mathematical calculations, so some things (like how negation "-" and







|
>
>







286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
.sp
\fBuexpr::unitNames\fR ?\fIglob pattern\fR?
.sp
\fBuexpr::unitsLike\fR ?\fIexpression\fR?
.sp
\fBuexpr::newUnit\fR \fIname\fR \fIexpression\fR
.sp
\fBuexpr::func\fR \fIname\fR \fIparameter_names\fR \fIexpression\fR
.sp
\fBuexpr::uset\fR \fIname\fR \fIexpression\fR
.sp
.BE
.SH DESCRIPTION
Package \fBuexpr\fR provides commands to evaluate and format
expressions that may include values with units\&. Its intended to be
used as part of a program to document engineering, physical and
mathematical calculations, so some things (like how negation "-" and
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
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








|







320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
Expressions consist of operands and operators sometimes separated by spaces\&.
.SH OPERANDS
Operands can be numbers or numbers with units, sometimes saved in
variables or a units dictionary\&. \fBuexpr\fR does not operate on
strings\&.  Comparisons in \fBuexpr\fR expressions return a numeric
1\&.0 for true, or 0\&.0 for false\&.
.SS NUMBERS
Numbers start with a decimal point or digit, and may include only one decimal point\&.
.CS

1 1\&.23 \&.123
.CE
They may include a 10's exponent after an e or E\&. The exponent may be signed\&.
.CS

344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360

3e +5e == 3*e+5*e
.CE
.CS

3E+ 5e == 3*E+5*e
.CE
Here the
space breaks up the scientific number definition, so both "e" and "E"
are interpreted as variables or units\&.
Keep this in mind if scientific notation is used in an expression and
units or variables named "e or "E" exist\&.
.SS "UNIT SPECIFIER"
A unit specifier is a simple expression containing only multiply (including
implied multiply) and divide operators, numbers, unit names and
variable names\&.
.CS







<
|
|







346
347
348
349
350
351
352

353
354
355
356
357
358
359
360
361

3e +5e == 3*e+5*e
.CE
.CS

3E+ 5e == 3*E+5*e
.CE

Here the space breaks up the scientific number definition, so both "e"
and "E" are interpreted as variables or units\&.
Keep this in mind if scientific notation is used in an expression and
units or variables named "e or "E" exist\&.
.SS "UNIT SPECIFIER"
A unit specifier is a simple expression containing only multiply (including
implied multiply) and divide operators, numbers, unit names and
variable names\&.
.CS
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415







416
417
418
419

420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457














458
459
460
461
462
463
464
followed by (or multiplied by) a unit specifier\&. They are returned
from \fBuexpr\fR, and can be saved in variables referenced by other
\fBuexpr\fR expressions\&.
.CS

100*BTU/hr ft^2
.CE
defines a heat flux rate\&.
.PP
Internally the value with units is a list containing a magnitude and
exponents on the basis units\&. For example using US Customary basis units: lbm, ft, sec, \&.\&.\&.
.CS

3ft/sec
.CE
gives a list starting with 3 0 1 -1 \&.\&.\&.
.CS

3m/sec
.CE
gives a list starting with 9\&.8425\&.\&. 0 1 -1 \&.\&.\&.
Note both values are speeds, and both have the same exponents on their basis units\&.
Compatible units have identical exponents on the basis units, and can
be added, subtracted and compared\&.
.SS "NAMED VALUES"
Named values can be variables from the context of the call to uexpr,
or units or constants from the units dictionary\&. Names must start with
a "_" or alphabetic character, and can contain "_" "\&." and
alphanumeric characters\&. If the variable is processed by \fBuexpr\fR, it
will be treated as a subexpression, and its result inserted into the
expression being evaluated\&. This allows variables to hold values with
units, which are returned from \fBuexpr\fR as simple expressions, not pure
numbers\&.
.SS VARIABLES
If a name follows a "$" it is a reference to a variable in the context
of the uexpr call or a local variable in a function definition\&. If a
name does not follow a "$" it may still be a variable (see NAME
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
.CE







|












|















|
|
|
|
>
>
>
>
>
>
>



|
>
|














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



|

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







377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443


















444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
followed by (or multiplied by) a unit specifier\&. They are returned
from \fBuexpr\fR, and can be saved in variables referenced by other
\fBuexpr\fR expressions\&.
.CS

100*BTU/hr ft^2
.CE
defines a heat flux\&.
.PP
Internally the value with units is a list containing a magnitude and
exponents on the basis units\&. For example using US Customary basis units: lbm, ft, sec, \&.\&.\&.
.CS

3ft/sec
.CE
gives a list starting with 3 0 1 -1 \&.\&.\&.
.CS

3m/sec
.CE
gives a list starting with 9\&.8425\&.\&.\&. 0 1 -1 \&.\&.\&.
Note both values are speeds, and both have the same exponents on their basis units\&.
Compatible units have identical exponents on the basis units, and can
be added, subtracted and compared\&.
.SS "NAMED VALUES"
Named values can be variables from the context of the call to uexpr,
or units or constants from the units dictionary\&. Names must start with
a "_" or alphabetic character, and can contain "_" "\&." and
alphanumeric characters\&. If the variable is processed by \fBuexpr\fR, it
will be treated as a subexpression, and its result inserted into the
expression being evaluated\&. This allows variables to hold values with
units, which are returned from \fBuexpr\fR as simple expressions, not pure
numbers\&.
.SS VARIABLES
If a name follows a "$" it is a reference to a variable in the context
of the uexpr call or a local variable in a function definition\&. If a
name does not follow a "$" it may still be a variable
(see \fBName Resolution\fR)\&. If a variable name follows "$" the whole
expression should be braced {}, to prevent Tcl from bypassing the
variable handling by \fBuexpr\fR\&.
.PP
Unlike the rest of Tcl, variable names referenced using "$" can also
include "_" and "\&." characters\&. See \fBName Resolution\fR\&.
.PP
Tcl array members (such as a(b3) can only be read using "$"\&. A
reference without the "$" prefix will be interpreted as a function
call, or an implied multiply of a variable by a subexpression\&.
.SS "UNITS AND CONSTANTS"
Units and constants are defined in a units dictionary, common to all
uexpr calls in an interpreter\&.  If a name follows a single ":" it will
only be looked up in the units dictionary\&.  If a unit or constant name
does not follow ":", it may be interpreted as a reference to a
variable, see \fBName Resolution\fR below\&.
.CS

:ft
.CE
look up the unit named ft in the units dictionary
.SS "NAME RESOLUTION"
If a name does not follow a "$" or ":" then the first value available
from the following list is used:
.IP \(bu
If the name is in an expression defining a function (see \fBuexpr::func\fR)
it is looked up in the function's parameter list
.IP \(bu
The name is looked up as a variable in the context of the uexpr call\&.
.IP \(bu


















The name is looked up in the units
dictionary\&.
.IP \(bu
An error occurs if no value was found\&.
.PP
If the name follows a "$" it is not looked up in the units
dictionary\&. A name following a "$" can be a Tcl array
member reference\&.
.CS

uexpr {3*$a(b)+1}
.CE
will multiply the value of the Tcl array "a" member "b" by 3 then add 1\&. Very similar to what Tcl \fBexpr\fR would do\&.
Without the "$" uexpr will interpret (b) as a subexpression or possibly part of a function call\&.
.CS

uexpr {3*a(b)+1}
.CE
where a is a variable (and not a function name) multiplies 3, a and b, then adds 1
.SS "NAMED VALUES AND IMPLIED MULTIPLY"
A number immediately followed by a named value will be interpreted as
if there was an implied multiply between them\&.
.CS

3ft+1inch
.CE
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495







496
497
498
499
500
501
502
503
504
505
506
507
508

509

510
511
512
513
514
515
516
517
518
519
520
521
522




523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558





















559


560
561





562




563
564

565
566
567
568
569
570
571
572
573
574
575
will be interpreted as diam/(3*ft)\&.
.SH OPERATORS
\fBuexpr\fR does not deal with integers and bitwise operators (& |
~ << >>), or boolean operators (! && ||) or strings (eq ne lt le ge
gt), or list searches (in ni)\&.
Operators roughly in descending order of precedence:
.IP \(bu
Variable\&.\&.(index expression list) index into a list or array in a variable\&.
.CS

set v [= 1,2,3,4]
.CE
.IP
saves a list in the variable v
.CS

= v\&.\&.2
.CE
.IP
returns the 3rd list entry (3 in this case)







.CS

set m [= (1,2,3),(11,22,33),(111,222,333)]
.CE
.IP
saves a 3x3 array in m
.CS

=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
multiplication\&. However their effective precedence is the maximum of
their precedence and the precedence of the operator to their left (if
any)\&. This means:
.CS

-2^2 == 0-2^2 == -4
.CE
.IP
This is not true for \fBexpr\fR, however it appears to match the conventions in
"Handbook of Mathematical Functions" by Abramowitz and Stegun\&. See the
definition of the error function for an example\&.
.IP \(bu
+ - ++ -- addition and subtraction, left and right arguments
must have compatible units\&. ++ and -- will also insert a line break in
LaTeX formatted output\&.
.IP \(bu
< <= =< == != <> \&.= => > comparisons, return 1\&.0 for true,
0\&.0 for false\&. note ==, != and <> are not that useful with real
numbers\&. Left and right arguments must have compatible units\&.
.sp
Comparisons can be chained\&. The result is true if all comparisons are true\&.
.CS

7<a<13
.CE
.IP
will return true (1\&.0) if the value of a is between 7 and 13
.IP \(bu
"," separate items in a list





















.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
"( )" group sub expressions (as for \fBexpr\fR)\&.

"%" is a constant defined as 0\&.01 in the units dict\&.
.CS

45%*a = 045*a
.CE
.SH "MATH FUNCTIONS"
Most math functions that work on real numbers in \fBexpr\fR are available in \fBuexpr\fR\&. However they are now unit aware\&.
.IP \(bu
Trigonometric Functions (sin cos tan) expect a single angle argument (deg rad
\&.\&.\&.)\&.and return a pure number\&.
.IP \(bu







|


|









>
>
>
>
>
>
>





|







>
|
>












|
>
>
>
>

|

|











|







|











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

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

|
>



|







481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
will be interpreted as diam/(3*ft)\&.
.SH OPERATORS
\fBuexpr\fR does not deal with integers and bitwise operators (& |
~ << >>), or boolean operators (! && ||) or strings (eq ne lt le ge
gt), or list searches (in ni)\&.
Operators roughly in descending order of precedence:
.IP \(bu
\&.\&. index into a list or nested lists in a variable\&.
.CS

set v {1 2 3 4}
.CE
.IP
saves a list in the variable v
.CS

= v\&.\&.2
.CE
.IP
returns the 3rd list entry (3 in this case)
note the list could have been generated using the \fBuexpr\fR "," operator too:
.CS

set v [= 1,2,3,4]
.CE
.IP
To index into nested lists use a list of index values:
.CS

set m [= (1,2,3),(11,22,33),(111,222,333)]
.CE
.IP
saves a 3x3 nested list in m
.CS

=m\&.\&.(2,0)
.CE
.IP
returns a value of 111\&.
.sp
This is rather fragile\&. If the indexes do not match the actual
nested list structure, there may not be any error, and odd results will be
returned\&.
.IP \(bu
** ^ exponentiation\&. The right argument (exponent) must be
unitless\&. The left argument can have units\&. However if the exponent is
not an integer, and the left operand has units, the result basis unit
exponents may not be integers\&. In some cases this can cause
problems\&. see \fBFractional or Non-Integer Exponents\fR\&.  Note
exponentiations group to the right so:
.CS

2^3^2 == 2^(3^2) == 512
.CE
.IP \(bu
"implied multiply" multiplication is implied any time two
values are not separated by an operator\&. A space between values is
common, however a number followed by a named value also implies
multiplication\&. Implied multiply has a higher precedence than multiply
or divide\&. Useful in unit specifiers\&.
.IP \(bu
/ divide, has same precedence as multiply\&.
.IP \(bu
* multiply, has same precedence as divide\&.
.IP \(bu
Unary + - operators to the left of their operand
have a minimum precedence between addition or subtraction and
multiplication\&. However their effective precedence is the maximum of
their precedence and the precedence of the operator to their left (if
any)\&. This means:
.CS

-2^2 == 0-2^2 == -4
.CE
.IP
This is not true for Tcl \fBexpr\fR, however it appears to match the conventions in
"Handbook of Mathematical Functions" by Abramowitz and Stegun\&. See the
definition of the error function for an example\&.
.IP \(bu
+ - ++ -- addition and subtraction, left and right arguments
must have compatible units\&. ++ and -- will also insert a line break in
LaTeX formatted output\&.
.IP \(bu
< <= =< == != <> >= => > comparisons, return 1\&.0 for true,
0\&.0 for false\&. note ==, != and <> are not that useful with real
numbers\&. Left and right arguments must have compatible units\&.
.sp
Comparisons can be chained\&. The result is true if all comparisons are true\&.
.CS

7<a<13
.CE
.IP
will return true (1\&.0) if the value of a is between 7 and 13
.IP \(bu
"," separate items in a list\&. Used to build lists and nested lists, such as lists of function arguments:
.CS

hypot(a,b)
.CE
.IP
Also can be used to generate lists to use with the \&.\&. operator\&. For
example coefficients for a polynomial using the sum function:
.CS

set C [uexpr 1,2,3,4]
.CE
.CS

set x 0\&.1
.CE
.CS

uexpr sum(i,0,3,C\&.\&.i*x^i)
.CE
.IP
will return 1\&.234
.IP \(bu
"=" marks a result unit specifier for an expression\&. For example:
.CS

uexpr 15 lbf/3 in^2=psi
.CE
.IP
would return "5\&.0 psi"
Multiple result specifiers can be given:
.CS

uexpr::ltxExpr {15 N / 5 cm^2 = bar = Pa}
.CE
.IP
would return results in bar and pascals\&.
.PP
"( )" group subexpressions (as for \fBexpr\fR)\&.
.PP
"%" is a constant defined as 0\&.01 in the units dict\&.
.CS

45%*a = 0\&.45*a
.CE
.SH "MATH FUNCTIONS"
Most math functions that work on real numbers in \fBexpr\fR are available in \fBuexpr\fR\&. However they are now unit aware\&.
.IP \(bu
Trigonometric Functions (sin cos tan) expect a single angle argument (deg rad
\&.\&.\&.)\&.and return a pure number\&.
.IP \(bu
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
.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







|


|



|






|







665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
.CS

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

set c [= 1,2,3,4,5]
.CE
.CS

set x 10
.CE
.CS

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

hasUnits(3)
.CE
.IP
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
then evaluates the expression and returns one result\&.
If the expression ends with "=" followed by a unit specifier, the
result is formatted to use those units\&. If the units specified do not
match the actual result units, they will be multiplied or divided by
enough base units to make them match\&. If multiple result unit
specifiers are given only the last is used\&. If no result units are specified,
the resulting unit expression will contain only base units\&. In any
case the result can be used as an input to uexpr\&.
.sp
.sp
\fB[uexpr]\fR functions similarly to \fBexpr\fR with several
significant differences\&. It is not a replacement for \fBexpr\fR\&.
For example:
.CS








|







720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
then evaluates the expression and returns one result\&.
If the expression ends with "=" followed by a unit specifier, the
result is formatted to use those units\&. If the units specified do not
match the actual result units, they will be multiplied or divided by
enough base units to make them match\&. If multiple result unit
specifiers are given only the last is used\&. If no result units are specified,
the resulting unit expression will contain only base units\&. In any
case the result can be used in an expression evaluated by uexpr\&.
.sp
.sp
\fB[uexpr]\fR functions similarly to \fBexpr\fR with several
significant differences\&. It is not a replacement for \fBexpr\fR\&.
For example:
.CS

735
736
737
738
739
740
741















742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
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:
.CS

uexpr::newUnit mile 5280*ft
.CE
.IP
Note, units are very similar to variables, except: Variables are only
looked up in the calling context of the expression evaluator\&. Unit
definitions are global, available to any instance of uexpr in the same
Tcl interpreter\&.
.TP
\fBuexpr::func\fR \fIname\fR \fIParameters\fR \fIexpression\fR
\fBuexpr::func\fR adds a new function, usable in any uexpr call in the
same interpreter\&. Variables in the expression that are not in the
parameter list, have the value of the variable in the calling context
at the time the function is defined\&. Variables in the parameter list
have values set at the time the function is used\&. If not all the
parameters are supplied in the function call, the value of a variable
with the same name (in the calling context) is used (it came from







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


|

|







|




|







786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
Return all unit names if no pattern is given\&.
.TP
\fBuexpr::unitsLike\fR ?\fIexpression\fR?
Returns a sorted
list of all units and constants with the same dimensions (compatible
units) as the result of the given expression\&. If no expression is
given, all unitless constants are returned\&.
.sp
For example if no new units are defined:
.CS

uexpr::unitsLike psi
.CE
.IP
returns: atm bar Pa pascal psi Torr
.sp
.CS

uexpr::unitsLike BTU/hr
.CE
.IP
returns: hp watt
.TP
\fBuexpr::newUnit\fR \fIname\fR \fIexpression\fR
\fBuexpr::newUnit\fR adds another unit to the unit dictionary\&. The
expression defines the new unit in terms of any existing base or defined
unit\&. The unit dictionary is common to all contexts within a Tcl
interpreter\&. These changes are global\&. For example a mile is defined
as:
.CS

uexpr::newUnit mile 5280*ft
.CE
.IP
Note, units are very similar to variables, except: variables are
looked up in the calling context of the expression evaluator\&. Unit
definitions are global, available to any instance of uexpr in the same
Tcl interpreter\&.
.TP
\fBuexpr::func\fR \fIname\fR \fIparameter_names\fR \fIexpression\fR
\fBuexpr::func\fR adds a new function, usable in any uexpr call in the
same interpreter\&. Variables in the expression that are not in the
parameter list, have the value of the variable in the calling context
at the time the function is defined\&. Variables in the parameter list
have values set at the time the function is used\&. If not all the
parameters are supplied in the function call, the value of a variable
with the same name (in the calling context) is used (it came from
811
812
813
814
815
816
817





818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839

set c 33
uexpr ss(4,5)

.CE
.IP
returns 12 (4+5+3), the change in c is ignored as it was not a function parameter





.PP
.SH "DIFFERENCES BETWEEN \FBUEXPR\FR AND \FBEXPR\FR"
\fBuexpr\fR is not a replacement for \fBexpr\fR\&.
.IP \(bu
numbers are all double precision\&. Mostly because many unit
conversion factors are not integers\&.
.IP \(bu
Variable names without "$" or ":" prefixes can be looked up in
the calling context or the units array or both:
.IP \(bu
variables used in expressions can contain complete expressions,
not just numbers\&. The variable contents will be evaluated and its
value used in the expression\&. This will likely not work if Tcl gets to the
variable first\&. Any time an expression refers to variables with the
"$" prefix the expression should be within braces\&.
.IP \(bu
a variable can contain a list or nested lists of unit
expressions\&. To get a value from a list, or nested list use the "\&.\&."
operator\&.
.IP \(bu
There are no bit or logical
operations (<< >> & | && ||)\&.







>
>
>
>
>







|
|




|
|







877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910

set c 33
uexpr ss(4,5)

.CE
.IP
returns 12 (4+5+3), the change in c is ignored as it was not a function parameter
.TP
\fBuexpr::uset\fR \fIname\fR \fIexpression\fR
\fBuexpr::uset\fR writes the result of evaluating an expression to a
variable, tcl array member or into a list or nested list saved in a
variable\&.
.PP
.SH "DIFFERENCES BETWEEN \FBUEXPR\FR AND \FBEXPR\FR"
\fBuexpr\fR is not a replacement for \fBexpr\fR\&.
.IP \(bu
numbers are all double precision\&. Mostly because many unit
conversion factors are not integers\&.
.IP \(bu
Variable names without "$" or ":" prefixes will be looked up in
the calling context or the units dictionary\&.
.IP \(bu
variables used in expressions can contain complete expressions,
not just numbers\&. The variable contents will be evaluated and its
value used in the expression\&. This will likely not work if Tcl gets to the
variable first\&. Any expression that refers to variables with the
"$" prefix should be within braces\&.
.IP \(bu
a variable can contain a list or nested lists of unit
expressions\&. To get a value from a list, or nested list use the "\&.\&."
operator\&.
.IP \(bu
There are no bit or logical
operations (<< >> & | && ||)\&.
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866

\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"\&.
Implied multiplies have higher precedence than normal
multiply (*) or divide (/)\&.
.CS







|
|







922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937

\fBuexpr::ltxExpr\fR 4*in+3*in=cm=ft
.CE
.IP \(bu
There are no type conversion functions (double entier,
wide)\&. int() exists, and returns a double with an integer value\&.
.IP \(bu
all functions available to uexpr and friends are in the
::uexpr::ufunc namespace\&.
.IP \(bu
When two values are not separated by an operator there is an
implied multiply\&. Since variable names cannot start with a number or
decimal point, "3a" will be interpreted as "3 a" or "5ft" as "5 ft"\&.
Implied multiplies have higher precedence than normal
multiply (*) or divide (/)\&.
.CS
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
consistent formula, the \fBuexpr\fR package can calculate the
volume in any desired units as long as h, w and l are all given in
length units\&.
.PP
Many common formulas use customary units\&. To use them with this
package they need to be written in self consistent form\&. One simple
way to do this is divide all variables in the expression by the units
specified\&. Multiply the expression by the specified result units\&.  For
example a formula to calculate liquid flow through a valve for simple cases (no
cavitation or flashing, low viscosity) is:
.CS

Q = Cv*sqrt(DP/Sg)
.CE
where







|







1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
consistent formula, the \fBuexpr\fR package can calculate the
volume in any desired units as long as h, w and l are all given in
length units\&.
.PP
Many common formulas use customary units\&. To use them with this
package they need to be written in self consistent form\&. One simple
way to do this is divide all variables in the expression by the units
specified, then multiply the expression by the specified result units\&.  For
example a formula to calculate liquid flow through a valve for simple cases (no
cavitation or flashing, low viscosity) is:
.CS

Q = Cv*sqrt(DP/Sg)
.CE
where
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
.CS

w = 0\&.09970190*C\&.d*Y*d^2*sqrt((DP*den)/(1-(d/D)^4))
.CE
The self consistent version of this formula is:
.CS

w = sqrt(2)*Cd*Y*pi/4*d^2*sqrt((DP*den)/(1-(d/D)^4))
.CE
the only vaguely mysterious number in the self consistent formula is sqrt(2)\&.
It will work with inputs and results in any compatible units\&. Meter inlet and
throat size in any combination of ft, in, cm\&.\&.\&. DP could be given as
psi, Pascal, bar, mm mercury \&.\&.\&. Density (den) could be given as
lbm/ft^3, gram/cm^3 \&.\&.\&. The resulting mass flow rate can be lbm/hr
gram/sec \&.\&.\&.
.PP
Often a formula for SI units will be self consistent, mass Kg,
lengths meter, time sec, force newton (N), pressure Pascal (Pa)
\&.\&.\&.\&. But it needs to be checked\&.
.SS "FRACTIONAL OR NON-INTEGER EXPONENTS"
In practical formulas, fractional exponents on values with units
rarely occur\&. Although mathematicians and physicists keep trying\&.
.PP
The internal representation of a value with units is a list of double
precision real numbers\&. The first entry is a magnitude, the others are
exponents on the basis units\&. All the unit definitions (initially, and







|










|







1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
.CS

w = 0\&.09970190*C\&.d*Y*d^2*sqrt((DP*den)/(1-(d/D)^4))
.CE
The self consistent version of this formula is:
.CS

w = sqrt(2)*C\&.d*Y*pi/4*d^2*sqrt((DP*den)/(1-(d/D)^4))
.CE
the only vaguely mysterious number in the self consistent formula is sqrt(2)\&.
It will work with inputs and results in any compatible units\&. Meter inlet and
throat size in any combination of ft, in, cm\&.\&.\&. DP could be given as
psi, Pascal, bar, mm mercury \&.\&.\&. Density (den) could be given as
lbm/ft^3, gram/cm^3 \&.\&.\&. The resulting mass flow rate can be lbm/hr
gram/sec \&.\&.\&.
.PP
Often a formula for SI units will be self consistent, mass Kg,
lengths meter, time sec, force newton (N), pressure Pascal (Pa)
\&.\&.\&. But it needs to be checked\&.
.SS "FRACTIONAL OR NON-INTEGER EXPONENTS"
In practical formulas, fractional exponents on values with units
rarely occur\&. Although mathematicians and physicists keep trying\&.
.PP
The internal representation of a value with units is a list of double
precision real numbers\&. The first entry is a magnitude, the others are
exponents on the basis units\&. All the unit definitions (initially, and
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
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







|


1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
polytropic process, where volume ratios are raised to powers between
1\&.4 and 1\&.1 (isentropic volume exponent)\&. Once again the volume ratio
is unitless, and there are no fractional exponents on the basis units\&.
.SH KEYWORDS
Calculations, uexpr
.SH COPYRIGHT
.nf
Copyright (c) 2021-2024 J\&.D Bruchie (BSD License)

.fi

Changes to uexpr.tcl.

9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

25
26
27
28
29
30
31
# 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)







|


|
|
<
|
|
<
>







9
10
11
12
13
14
15
16
17
18
19
20

21
22

23
24
25
26
27
28
29
30
# Internally a value with units (uval) is a list,
# the first element is a magnitude,
# the rest are exponents on the base dimensions:
#   Length Mass Time Charge Degree ...
#
# A uvalue is string representation of a uval
# It is a more readable expression starting with a number
# multiplied and divded by units (from the units dictionary or named variables).
# Results from uexpr are returned as uvalues.
#
# currently lists or nested lists of uvalues can be indexed into
# they are vaguely like vectors or arrays




# Ideas for vector and array calculations (not implemented):
# arrays are a list of:
#   the array depth (0=uval, 1=vector, 2=matrix ...)
#   the nested list of uvals
#     alternate could be nested list of values with shared units
#     gives faster but less general math
# a matrix (2d array) is a list of rows
#   (compatable-ish with struct::matrix serailze)
67
68
69
70
71
72
73








74
75
76
77
78
79
80
#   otherwise error occurs (incompatible array shapes)
#   allows easy biasing of vector or matrix
#   could also:
#     add identity value to shorter value
#     (0 for add/sub, 1 for multiply/divide...)
#     repeat the shorter value list as needed (use case ??)
#








# Results are converted back to a simple unit expression string like "1 ft"
#
# basic math functions are:
#
# add (+) subtract (-) -- error if dimensions do not match, operate on value
# mul (* or space) divide (/) -- add/subtract dimensions, operate on value
# power (^ or **) -- operate on value, multiply dimensions by exponent







>
>
>
>
>
>
>
>







66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#   otherwise error occurs (incompatible array shapes)
#   allows easy biasing of vector or matrix
#   could also:
#     add identity value to shorter value
#     (0 for add/sub, 1 for multiply/divide...)
#     repeat the shorter value list as needed (use case ??)
#
# vector [list of uexprs] and matrix (list of lists of uexprs)
# name..<index expr>
# index expr can be a number, a variable, or (<index expr>,<index expr, ...)
# first access sets number of dimensions
# use \U21BC (leftward harpoon with barb upwards) as operator symbol



# Results are converted back to a simple unit expression string like "1 ft"
#
# basic math functions are:
#
# add (+) subtract (-) -- error if dimensions do not match, operate on value
# mul (* or space) divide (/) -- add/subtract dimensions, operate on value
# power (^ or **) -- operate on value, multiply dimensions by exponent
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129

130
131
132
133
134

135




136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# uexpr "10 gal = in^2"
# returns:
# 192.499...997 ft * in^2
#
# operators specific to uexpr:
#  $ following name is a variable in the calling context
#  : following name is a unit
#  .. separates a 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)
# or metric {gm m sec Coul degK deg gmole)
# or any other self consistant set (see newBaseUnit and uBaseNames)
#
# names (variables or functions)  can include symbols and subscripts
# (and unicode)
# They cannot start with a diget or ".", but can contain them.
# _ followed by a letter selects a symbol, typically a greek char
#   (in LaTeX output).
# . indicates any following chars are part of a subscript (in LaTeX output).
#   variables are looked up in the following sequence:
#      as a local variable (in locals dict)
#      in the calling contex as a variable
#      in the calling context as a TCL array member (if there is a subscript)
#         where the first dot in the name (if any)
#         separates array and member names
#      finally as a unit (in the units dict)
#  variable names preceded by "$" are only looked up in the calling context
#    (brace {} the expression). For example uexpr {3*$x}
# variable names preceeded by ":" are only looked up in the units dict,
# for example:
#  uexpr {5:ft + 3:in}


# for example: _r.0 could be:
#  a local variable _r.0 (from a function parameter list)
#  a variable in the calling context _r.0
#  or an array element in the calling context _r(0)
#  or a unit _r.0
#  LaTeX generated for: _r.0 would be: \rho _\mathrm{0}

# vector [list of uexprs] and matrix (list of lists of uexprs)
# name..<index expr>
# index expr can be a number, a variable, or (<index expr>,<index expr, ...)
# first access sets number of dimensions
# use \U21BC (leftward harpoon with barb upwards) as operator symbol

# Most math functions defined by Tcl expr are defined here to operate with
# unit values.
# Trig functions expect units of angle (deg, rad)
# Some functions expect unitless values (asin() acos() ...)
# Some accept any units (sqrt() abs() ...)
#
# Additional Functions can be defined (or existing functions redefined) 







|
|
|

|
|
|
|
<
<
|
>

|
|
|
|
>
|
>
>
>
>



















<
<
<
|


|







<



<
<
<
<
<
<







119
120
121
122
123
124
125
126
127
128
129
130
131
132
133


134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165



166
167
168
169
170
171
172
173
174
175
176

177
178
179






180
181
182
183
184
185
186
# uexpr "10 gal = in^2"
# returns:
# 192.499...997 ft * in^2
#
# operators specific to uexpr:
#  $ following name is a variable in the calling context
#  : following name is a unit
#  .. separates a variable containing a list or nested lists from an index expression
#     an index expression can be a single value v..3
#     or a list, such as a..(i,j)  or  a..(1,i,2)
# ++ is a line break in an equation at a + operator
#      it is converted to \U229E (boxed +) then parsed to +CR:
#         if left op is +CR,  return left op with + and right op appended
#         otherwise return {+CR leftOp + rightOp}
#      +CR evaluates to sum the values from its list (ignoring the +/- operators)


#      +CR generates LaTeX for an array with one operand per line,
#          separated by the given + or - operators
#  -- is a line break at a subtract
#      it is mapped to \U229F (boxed -) and \U2296 (circled -)
#      when \U229F is parsed:
#          if left op is +CR, append "-" and right op to left op operand list and return it.
#          otherwise return {+CR leftOp - rightOp}
#      \U2296 (circled -) is parsed to an invisible negate (ineg)
#           ineg negates its operand during evaluation,
#           but generates no LaTeX output
#           Precedence is higher than +/- but lower than negate or multiply/divide
#           It is used to capture and negate the first +/- precedence operand after a -- operator
#           so the +CR operator can just add its operands during evaluation.
#
# all numbers are real because:
#   unit conversions can force numbers to be real,
#   so for consistancy, all are real
# real numbers can be: 1.23e3  1.23*10^3  or  1230
#
# base unts can be US customary {lbm ft sec Coul degR deg lbmole)
# or metric {gm m sec Coul degK deg gmole)
# or any other self consistant set (see newBaseUnit and uBaseNames)
#
# names (variables or functions)  can include symbols and subscripts
# (and unicode)
# They cannot start with a diget or ".", but can contain them.
# _ followed by a letter selects a symbol, typically a greek char
#   (in LaTeX output).
# . indicates any following chars are part of a subscript (in LaTeX output).
#   variables are looked up in the following sequence:
#      as a local variable (in locals dict)
#      in the calling contex as a variable



#      finally as a unit (in the units dictionary)
#  variable names preceded by "$" are only looked up in the calling context
#    (brace {} the expression). For example uexpr {3*$x}
# variable names preceeded by ":" are only looked up in the units dictionary,
# for example:
#  uexpr {5:ft + 3:in}


# for example: _r.0 could be:
#  a local variable _r.0 (from a function parameter list)
#  a variable in the calling context _r.0

#  or a unit _r.0
#  LaTeX generated for: _r.0 would be: \rho _\mathrm{0}







# Most math functions defined by Tcl expr are defined here to operate with
# unit values.
# Trig functions expect units of angle (deg, rad)
# Some functions expect unitless values (asin() acos() ...)
# Some accept any units (sqrt() abs() ...)
#
# Additional Functions can be defined (or existing functions redefined) 
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
# If the operator left prec is higher, it parses a right value
# (at its right precedence).
# Then It returns:
#  a new value (including itself as operator and its operands),
#  and its left precedence.
# Its token has been used.

# When parsing tokes as values:
# They get a precedence, and return a value at that precedence.
# In most cases applyOp is used to apply any further operators
# to the right of the value at the originally given precedence.

# "(" and ")" can appear as value or operator tokens (see details below)

package provide uexpr 0.1


namespace eval ::uexpr {







|


|







277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
# If the operator left prec is higher, it parses a right value
# (at its right precedence).
# Then It returns:
#  a new value (including itself as operator and its operands),
#  and its left precedence.
# Its token has been used.

# When parsing tokens as values:
# They get a precedence, and return a value at that precedence.
# In most cases applyOp is used to apply any further operators
# to the right of the value with left precedence higher than the given precedence.

# "(" and ")" can appear as value or operator tokens (see details below)

package provide uexpr 0.1


namespace eval ::uexpr {
339
340
341
342
343
344
345

346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
    }

    # information common to all instances of uexpr
    variable sys
    # map multichar and annoying symbols (: to right arrow) to single char
    # trying to define a proc named : in a namespace
    # does not seem to work with ensembles

    set sys(symmap) [list "==" \U2248  \
			 "!=" \U2249  "<>" \U2249  \
			 "<=" \U2264  "=<" \U2264  \
			 ">=" \U2265  "=>" \U2265  \
			 ** ^  \
			 ++ \U229E  \
			 -- \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]


    # utility and debugging stuff








>






|



|







340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
    }

    # information common to all instances of uexpr
    variable sys
    # map multichar and annoying symbols (: to right arrow) to single char
    # trying to define a proc named : in a namespace
    # does not seem to work with ensembles
    # note -- maps to two operator chars (boxed- and circled -)
    set sys(symmap) [list "==" \U2248  \
			 "!=" \U2249  "<>" \U2249  \
			 "<=" \U2264  "=<" \U2264  \
			 ">=" \U2265  "=>" \U2265  \
			 ** ^  \
			 ++ \U229E  \
			 -- \U229F\U2296  \
			 : \U2192  \
			 .. \U21BC]
    # operator symbol chars (and %)
    set sys(opchar) " \n\r\t()$\U2192\U21BC%^*/<>=+,-\U2248\U2249\U2264\U2265\U2296\U229E\U229F"
    # nested list operators that have only literal operands (no further nesting)
    # uval only occurs within user defined functions
    set sys(literal) [list num name varName unitName funcName uval]


    # utility and debugging stuff

431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
    }


    
    proc tokenize {str} {
	variable sys
	
	# merge multi char symbols into a single char
	# break around any recognized operator/function character,
	#  "%" or white space
	set words [spliti  [string map $sys(symmap) $str]  $sys(opchar)]

	dm {tokenize words $words}

	# check for special cases where words start with a number







|







433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
    }


    
    proc tokenize {str} {
	variable sys
	
	# merge multi char symbols into a single char (2 different chars for -- operator)
	# break around any recognized operator/function character,
	#  "%" or white space
	set words [spliti  [string map $sys(symmap) $str]  $sys(opchar)]

	dm {tokenize words $words}

	# check for special cases where words start with a number
508
509
510
511
512
513
514



















515
516
517
518
519
520
521
			 [string range $word $ii end]]
		# skip new list entry
		incr n
	    }
	    # continue search at word after the one just processed
	    incr n
	}




















	# return list after removing any remaining blank or empty strings
	lmap word $words {
	    if {[string is space $word]} {continue}
	    set word
	}
    }







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







510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
			 [string range $word $ii end]]
		# skip new list entry
		incr n
	    }
	    # continue search at word after the one just processed
	    incr n
	}

	# look for tcl array references in list: $ <name> (
	# with no spaces between elements
	# collapse the name and everything between the following ( and next )
	# into the name as an array member reference.
	# Note there can be any number of "(" and spaces in a member name.
	# This appears to match how Tcl (and Tcl expr) parses array member names
	set i -2
	while {-1 != [set i [lsearch -start $i+2 -exact $words "$"]]} {
	    # if this is a reference to a Tcl array variable member
	    if {"(" eq [lindex $words $i+2]} {
		# everything up to and including the next ) is part of the array reference
		set ii [lsearch -start $i+3 -exact $words ")"]
		if {-1 == $ii} {
		    error "[join [lrange $words $i end] {}] is not a valid Tcl array reference"
		}
		set words [lreplace $words $i+1 $ii [join [lrange $words $i+1 $ii] ""]]
	    }
	}

	# return list after removing any remaining blank or empty strings
	lmap word $words {
	    if {[string is space $word]} {continue}
	    set word
	}
    }
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
	#    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]
    }


    # tokens are kept in a variable (tokens) in the context of the parse command
    # During processing n has the index of the last token processed
    # It is also in the context of the parsing command







|







555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
	#    perhaps rename lvl to parserContext ???

	set tokens [tokenize $str]
	dm {parse $tokens}
	# index to last token used
	set n -1
	set lvl #[info level]
	# get first value in the expression, then apply it to the following operators
	applyOp $lvl 1 [parseValue [nextToken $lvl] $lvl 1]
    }


    # tokens are kept in a variable (tokens) in the context of the parse command
    # During processing n has the index of the last token processed
    # It is also in the context of the parsing command
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
    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

    # TCL and excel... puts negation higher precedence than
    # any other inline binary operator
    # in particular: = 2^-3*5
    # would be 2^-15 with prec = 21
    # should be 0.625 or 5/8
    # however -2^2 = 4







|
|







637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
    setOp > 20 21
    # generic chained compare operator
    setOp CC 20 21

    setOp + 30 31
    setOp - 30 31
    setOp +CR 28 29
    # invisible negate (part of -- operator)
    setOp ineg "" 33

    # TCL and excel... puts negation higher precedence than
    # any other inline binary operator
    # in particular: = 2^-3*5
    # would be 2^-15 with prec = 21
    # should be 0.625 or 5/8
    # however -2^2 = 4
718
719
720
721
722
723
724
















725
726
727
728
729
730
731
		 neg \
		 [parseValue [nextToken $lvl] \
		      $lvl \
		      [::tcl::mathfunc::max $prec $rprec(neg)]]]
    }


















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







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







739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
		 neg \
		 [parseValue [nextToken $lvl] \
		      $lvl \
		      [::tcl::mathfunc::max $prec $rprec(neg)]]]
    }



    # circled -
    proc parseValue::\U2296 {lvl prec} {
	# invisible negate (at slightly above +/- precedence)
	# used as part of --
	# fairly generic for any operator to left of its operand
	# however there is only invisible negate for now
	variable rprec
	applyOp $lvl $prec \
	    [list   \
		 ineg   \
		 [parseValue  [nextToken $lvl]  $lvl  $rprec(neg)]]
    }

    

    proc parseValue::+ {lvl prec} {
	# unary +
	variable rprec
	# get value, but with adjusted precedence
	# currently not adding a {pos ...} wrapper
	tailcall parseValue \
	    [nextToken $lvl] \
752
753
754
755
756
757
758
759
760
761

762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
	    error "no matching ')'"
	}
	# apply value to trailing operators of higher precedence
	applyOp $lvl $prec $val 
    }


    # following name is a variable
    # Note $ does not have an entry in rprec or lprec
    # because it always gets just the next token

    proc parseValue::\$ {lvl prec} {
	set name [nextToken $lvl]
	set char1 [string index $name 0]
	# check that the following string can be a valid variable
	# (do not check that it exists)
	# apply the variable to the next operator (if its precedence is higher)
	applyOp $lvl $prec \
	    [if {[string is alpha -strict $char1]
		 || [string match "_" $char1]} {
		# valid variable name
		list varName $name
	    } else {
		error "$name is not a valid variable name (after $)"
	    }]
    }


    # following name is a unit
    # : is mapped to unicode right arrow (\U2192)
    # Note $: (\U2192) does not have an entry in rprec or lprec
    # because it always gets the next token
    proc parseValue::\U2192 {lvl prec} {
	set name [nextToken $lvl]
	set char1 [string index $name 0]
	# check that the following string can be a valid unit
	# (do not check that it exists)
	# apply the unit to the next operator (if its precedence is higher)







|


>












|






|







789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
	    error "no matching ')'"
	}
	# apply value to trailing operators of higher precedence
	applyOp $lvl $prec $val 
    }


    # following name is a Tcl variable name
    # Note $ does not have an entry in rprec or lprec
    # because it always gets just the next token
    # Much of the parsing work is done in [tokenize]
    proc parseValue::\$ {lvl prec} {
	set name [nextToken $lvl]
	set char1 [string index $name 0]
	# check that the following string can be a valid variable
	# (do not check that it exists)
	# apply the variable to the next operator (if its precedence is higher)
	applyOp $lvl $prec \
	    [if {[string is alpha -strict $char1]
		 || [string match "_" $char1]} {
		# valid variable name
		list varName $name
	    } else {
		error "$name is not a valid variable name"
	    }]
    }


    # following name is a unit
    # : is mapped to unicode right arrow (\U2192)
    # Note : (\U2192) does not have an entry in rprec or lprec
    # because it always gets the next token
    proc parseValue::\U2192 {lvl prec} {
	set name [nextToken $lvl]
	set char1 [string index $name 0]
	# check that the following string can be a valid unit
	# (do not check that it exists)
	# apply the unit to the next operator (if its precedence is higher)
796
797
798
799
800
801
802
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







<

>







834
835
836
837
838
839
840

841
842
843
844
845
846
847
848
849
	    }]
    }


    proc parseValue::\) {lvl prec} {
	# ) is normally an operator,
	# however this could be the end of a function call with no operands

	variable rprec
	# if not within a function call
	if {$prec != $rprec(\()} {
	    # an empty last value in a list (1,) will cause an error here !!!
	    # which is okay for now
	    error "not within function call"
	}
	# always restore token to match normal state at end of function parsing
	reuseToken $lvl
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
	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]  \







|







945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
	variable lprec
	dm {parseChainedCompare $op $lvl  prec: $prec  lvalue:$lvalue}
	# if operator left prec is greater than or equal to subexpression prec
	if {$prec <= $lprec(CC)} {
	    # if left operand is CC
	    if {"CC" eq [lindex $lvalue 0]} {
		# add to existing CC list
		lappend lvalue [list funcName $op] [parseValue [nextToken $lvl] $lvl $rprec(CC)]
	    } else {
		# apply CC (and its operator and right subexpression value)
		# to the current subexpression
		set lvalue [list \
				CC \
				$lvalue \
				[list funcName $op]  \
935
936
937
938
939
940
941


942
943
944
945
946
947
948
    interp alias  {} parseOp::\U2249  {} ::uexpr::parseChainedCompare \U2249
    interp alias  {} parseOp::<  {} ::uexpr::parseChainedCompare <
    interp alias  {} parseOp::\U2264  {} ::uexpr::parseChainedCompare \U2264
    interp alias  {} parseOp::\U2265  {} ::uexpr::parseChainedCompare \U2265
    interp alias  {} parseOp::>  {} ::uexpr::parseChainedCompare >




    proc parse+CR {op lvl prec lvalue} {
	# line break at + or -
	# leaves a +CR command followed by first left op
	# then pairs of operatot (=/-) and right operand. For example
	#    3 ++ 4 -- 5
	# yields
	#    +cr 3 + 4 - 5







>
>







973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
    interp alias  {} parseOp::\U2249  {} ::uexpr::parseChainedCompare \U2249
    interp alias  {} parseOp::<  {} ::uexpr::parseChainedCompare <
    interp alias  {} parseOp::\U2264  {} ::uexpr::parseChainedCompare \U2264
    interp alias  {} parseOp::\U2265  {} ::uexpr::parseChainedCompare \U2265
    interp alias  {} parseOp::>  {} ::uexpr::parseChainedCompare >


    # !!! needss work -- causes whole following block to be subtracted
    # see test +CR-11
    proc parse+CR {op lvl prec lvalue} {
	# line break at + or -
	# leaves a +CR command followed by first left op
	# then pairs of operatot (=/-) and right operand. For example
	#    3 ++ 4 -- 5
	# yields
	#    +cr 3 + 4 - 5
1217
1218
1219
1220
1221
1222
1223
1224

1225
1226
1227
1228
1229
1230
1231
	dm {parseOp::arrayAccess $lvl  prec: $prec  lvalue:$lvalue}
	# arrayAccess left prec is always greater than left subexpression prec
	if {$prec > $lprec(arrayAccess)} {
	    error "left prec of array access ($lprec(arrayAccess)) \
                   should always be larger than $prec"
	}
	# verify left subexpr is a name
	if {"name" ne [lindex $lvalue 0]} {

	    error "parsing arrayAccess expects a name to left, not $lvalue"
	}
	# get right value
	set rvalue [parseValue [nextToken $lvl] $lvl $rprec(\U21BC)]
	# if right value is not a list subexpr, make it one
	if {"subexpr" eq [lindex $rvalue 0]} {
	    if {"vlist" eq [lindex $rvalue 1 0]} {







|
>







1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
	dm {parseOp::arrayAccess $lvl  prec: $prec  lvalue:$lvalue}
	# arrayAccess left prec is always greater than left subexpression prec
	if {$prec > $lprec(arrayAccess)} {
	    error "left prec of array access ($lprec(arrayAccess)) \
                   should always be larger than $prec"
	}
	# verify left subexpr is a name
	if {[lindex $lvalue 0] ni {name varName}} {
	    #puts "lvalue $lvalue"
	    error "parsing arrayAccess expects a name to left, not $lvalue"
	}
	# get right value
	set rvalue [parseValue [nextToken $lvl] $lvl $rprec(\U21BC)]
	# if right value is not a list subexpr, make it one
	if {"subexpr" eq [lindex $rvalue 0]} {
	    if {"vlist" eq [lindex $rvalue 1 0]} {
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949

1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967

1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001

2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049

    # generally eval and ltx versions are next to each other
    # to insure consistancy
    
    # name -- evaluate anything not an operator or a number
    # First searches for a local variable (returns uval from locals dict
    # in uExpr context)
    # then 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"
	}
    }


    
    proc eval::setName {name value} {
	# searches for variable in same way as varName
	# (without checking for a unit value)
	# an undefined variable is defined in the callingLevel context
	# saves value in variable refered to
	dm {name $name level: [info level]}
	# 2 levels to calling proc,
	# one level for this proc, one for namespace eval
	upvar 2 callingLevel callingLevel  locals locals  lvl lvl
	dm {in name $name  callingLevel=$callingLevel}
	
	# 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 a function context
	# note local vars are all defined before first reference
	if {[dict exists $locals $name]} {
	    dict set locals $name $value
	    # variable exists in calliongLevel context
	} elseif {[uplevel $callingLevel [list info exists $name]]} {
	    uplevel $callingLevel [list set $name $value]
	    # if its an array ref, aaa.bb means aaa(bb), and aaa means aaa()
	} elseif {[uplevel $callingLevel [list info exists ${aname}($mname)]]} {
	    uplevel $callingLevel [list set ${aname}($mname) $value]
	    # may have problems in calc.tcl if aaa is defined before aaa.bb
	} else {
	    uplevel $callingLevel [list set $name $value]
	}
	return $value
    }









|
<
<
|



>












<
<
<
|


>




<
<
<
<
<


















|
|
<
<
<


>




<
<
<
<
<


















<
<
<




<
<
<
<
<
<
<







1977
1978
1979
1980
1981
1982
1983
1984


1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001



2002
2003
2004
2005
2006
2007
2008
2009





2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029



2030
2031
2032
2033
2034
2035
2036





2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054



2055
2056
2057
2058







2059
2060
2061
2062
2063
2064
2065

    # generally eval and ltx versions are next to each other
    # to insure consistancy
    
    # name -- evaluate anything not an operator or a number
    # First searches for a local variable (returns uval from locals dict
    # in uExpr context)
    # then as a variable in the calling context


    # if a variable is not found, look for a unit with the given name
    # In any case, the value is an expression which is parsed and evaluated
    # to return a uval
    # Elsewhere can only search for variable names ("$" varName) or units (":")
    # note Tcl array members can only be referenced using $
    # A name can also be a function name in the nested list.
    # In that case this function is not executed.
    # For LaTeX output only the variable name is used

    proc eval::name {name} {
	dm {name $name level: [info level]}
	# 2 levels to calling proc for context
	# one level for this proc, one for namespace eval
	upvar 2 callingLevel callingLevel  locals locals

	dm {in name $name  callingLevel=$callingLevel}
	



	# if its a function local variable name 
	if {[info exists locals] && [dict exists $locals $name]} {
	    return [dict get $locals $name]
	# if its a variable name in the calling scope
	} elseif {![catch [list upset $callingLevel $name] value]} {
	    # convert to a unit list value
	    dm {variable name=$name val=$value}
	    return [namespace eval ::uexpr::eval [tclizeExpr $value]]





	    # if its a unit name
	} elseif {0 == [catch {set ::uexpr::units($name)} value]} {
	    dm {unit name=$name val=$value}
	    return $value
	} else {
	    error "uExpr: $name is not a variable or unit name"
	}
    }
    
    
    
    proc eval::varName {name} {
	# same as name, except without checking for a unit value
	dm {name $name level: [info level]}
	# 2 levels to calling proc,
	# one level for this proc, one for namespace eval
	upvar 2 callingLevel callingLevel  locals locals
	dm {in name $name  callingLevel=$callingLevel}

	# if it is a local variable



	if {[dict exists $locals $name]} {
	    return [dict get $locals $name]
	# if its a variable name in the calling scope
	} elseif {![catch [list upset $callingLevel $name] value]} {
	    # convert to a unit list value
	    dm {variable name=$name val=$value}
	    return [namespace eval ::uexpr::eval [tclizeExpr $value]]





	} else {
	    error "uExpr: $name is not a variable name"
	}
    }


    
    proc eval::setName {name value} {
	# searches for variable in same way as varName
	# (without checking for a unit value)
	# an undefined variable is defined in the callingLevel context
	# saves value in variable refered to
	dm {name $name level: [info level]}
	# 2 levels to calling proc,
	# one level for this proc, one for namespace eval
	upvar 2 callingLevel callingLevel  locals locals  lvl lvl
	dm {in name $name  callingLevel=$callingLevel}
	



	# if its a variable name in a function context
	# note local vars are all defined before first reference
	if {[dict exists $locals $name]} {
	    dict set locals $name $value







	} else {
	    uplevel $callingLevel [list set $name $value]
	}
	return $value
    }


2070
2071
2072
2073
2074
2075
2076
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
    proc eval::arrayAccess {name indexList} {
	# save value in named variable
	dm {arrayAccess $name level: [info level]}
	# 2 levels to calling proc,
	# one level for this proc, one for namespace eval
	upvar 2 callingLevel callingLevel  locals locals  lvl lvl
	dm {in arrayAccess $name $indexList  callingLevel=$callingLevel}
	# generate possible array variable and member names
	# member name is everything after the first "."
	set mname [join [lassign [split $name "."] aname] "."]
	# if its a function local variable
	if {[info exists locals] && [dict exists $locals $name]} {
	    set val [lindex [dict get $locals $name] {*}$indexList]
	    if {"" eq $val} {
		error "uExpr: index $indexList out of range for array $name"
	    }
	    return $val
	    # if its a variable name in the calling scope
	} elseif {![catch [list upset $callingLevel $name] value]} {
	    # convert to a unit list value
	    dm {variable name=$name  index=$indexList val=$value}
	    # if its an array ref, aaa.bb means aaa(bb), and aaa means aaa()
	} elseif {![catch [list upset $callingLevel ${aname}($mname)] value]} {
	    dm {variable array=$aname  member=$mname  index=$indexList val=$value}
	    # may have problems in calc.tcl if aaa is defined before aaa.bb
	} else {
	    error "uExpr: $name with index $indexList\
                   is not an array variable name"
	}
	set val [lindex $value {*}$indexList]
	if {"" eq $val} {
	    error "uExpr: array $name has no value for index $indexList"
	}
	# get uval from expression in array variable
	namespace eval ::uexpr::eval [tclizeExpr $val]
    }
    


    proc eval::setArrayAccess {name indexList value} {
	# save value in nested list in named variable
	dm {arrayAccess $name level: [info level]}
	# 2 levels to calling proc
	# one level for this proc, one for namespace eval
	upvar 2 callingLevel callingLevel  locals locals  lvl lvl
	dm {in arrayAccess $name $indexList  callingLevel=$callingLevel}
	# 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 a local variable in a function
	if {[info exists locals] && [dict exists $locals $name]} {
	    set val [dict set $locals $name]
	    if {[catch {lset val {*}$indexList $value}]} {
		error "cannot set list in local variable $name\
                       at index [join $indexList ,]"
	    }
	    dict set locals $name $val
	# if its a variable name in the calling context
	} elseif {[uplevel $callingLevel [list info exists $name]]} {
	    if {[catch {uplevel $callingLevel \
			    [concat \
				 [list lset $name] \
				 $indexList \
				 [list $value]]}]} {
		error "cannot set list entry at index [join $indexList ,]\
                       in variable $name"
	    }
	    # if its an array ref, aaa.bb means aaa(bb), and aaa means aaa()
	} elseif {"" ne [uplevel $callingLevel \
			     [list array names ${aname} -exact $mname]]} {
	    if {[catch {uplevel $callingLevel \
			    [list lset $aname($mname) {*}$indexList $value]}]} {
		error "cannot set list entry at index [join $indexList ,]\
                       in member $mname of Tcl array $name"
	    }
	    # may have problems in calc.tcl if aaa is defined before aaa.bb
	} else {
	    # variable does not exist
	    # save value, to be returned
	    set val $value
	    foreach i $indexList {
		# index list must be all 0's
		if {0 != $i} {







<
<
<








|
<
<
<
<
<
<
<

|


















<
<
<


















<
<
<
<
<
<
<
<
<







2086
2087
2088
2089
2090
2091
2092



2093
2094
2095
2096
2097
2098
2099
2100
2101







2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121



2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139









2140
2141
2142
2143
2144
2145
2146
    proc eval::arrayAccess {name indexList} {
	# save value in named variable
	dm {arrayAccess $name level: [info level]}
	# 2 levels to calling proc,
	# one level for this proc, one for namespace eval
	upvar 2 callingLevel callingLevel  locals locals  lvl lvl
	dm {in arrayAccess $name $indexList  callingLevel=$callingLevel}



	# if its a function local variable
	if {[info exists locals] && [dict exists $locals $name]} {
	    set val [lindex [dict get $locals $name] {*}$indexList]
	    if {"" eq $val} {
		error "uExpr: index $indexList out of range for array $name"
	    }
	    return $val
	    # if its a variable name in the calling scope
	} elseif {[catch [list upset $callingLevel $name] value]} {







	    error "uExpr: $name with index $indexList\
            	         is not an array variable name"
	}
	set val [lindex $value {*}$indexList]
	if {"" eq $val} {
	    error "uExpr: array $name has no value for index $indexList"
	}
	# get uval from expression in array variable
	namespace eval ::uexpr::eval [tclizeExpr $val]
    }
    


    proc eval::setArrayAccess {name indexList value} {
	# save value in nested list in named variable
	dm {arrayAccess $name level: [info level]}
	# 2 levels to calling proc
	# one level for this proc, one for namespace eval
	upvar 2 callingLevel callingLevel  locals locals  lvl lvl
	dm {in arrayAccess $name $indexList  callingLevel=$callingLevel}



	# if its a variable name in a local variable in a function
	if {[info exists locals] && [dict exists $locals $name]} {
	    set val [dict set $locals $name]
	    if {[catch {lset val {*}$indexList $value}]} {
		error "cannot set list in local variable $name\
                       at index [join $indexList ,]"
	    }
	    dict set locals $name $val
	# if its a variable name in the calling context
	} elseif {[uplevel $callingLevel [list info exists $name]]} {
	    if {[catch {uplevel $callingLevel \
			    [concat \
				 [list lset $name] \
				 $indexList \
				 [list $value]]}]} {
		error "cannot set list entry at index [join $indexList ,]\
                       in variable $name"
	    }









	} else {
	    # variable does not exist
	    # save value, to be returned
	    set val $value
	    foreach i $indexList {
		# index list must be all 0's
		if {0 != $i} {
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
    proc eval::vlist {args} {
	# returns a list of uvals (list of lists)
	return $args
    }


    proc ltx::vlist {args} {
	join $args { }
    }
    
    
    # map symbol char to LaTeX
    # !! check against LaTeX
    
    set ltx::syms [dict create \







|







2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
    proc eval::vlist {args} {
	# returns a list of uvals (list of lists)
	return $args
    }


    proc ltx::vlist {args} {
	join $args {, }
    }
    
    
    # map symbol char to LaTeX
    # !! check against LaTeX
    
    set ltx::syms [dict create \
2370
2371
2372
2373
2374
2375
2376

2377
2378
2379
2380
2381
2382









2383
2384
2385
2386
2387
2388
2389
    # procs for each operator to generate a value in eval namespace
    # procs to generate LaTeX in ltx namespace

    proc eval::neg {u} {
	dm neg
	lreplace $u 0 0 [expr -[lindex $u 0]]
    }


    proc ltx::neg {u} {
	string cat  "-" $u
    }












    proc ltx::BinaryOp {op args} {
	lassign $args a b
	string cat $a $op $b
    }

    







>






>
>
>
>
>
>
>
>
>







2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
    # procs for each operator to generate a value in eval namespace
    # procs to generate LaTeX in ltx namespace

    proc eval::neg {u} {
	dm neg
	lreplace $u 0 0 [expr -[lindex $u 0]]
    }


    proc ltx::neg {u} {
	string cat  "-" $u
    }


    # invisible negate (part of -- operator)
    
    interp alias {} eval::ineg  {} ::uexpr::eval::neg

    proc ltx::ineg {u} {
	# the latex code is handled by \U229F (via +CR -)
	return $u
    }
    

    proc ltx::BinaryOp {op args} {
	lassign $args a b
	string cat $a $op $b
    }

    
2506
2507
2508
2509
2510
2511
2512


2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
    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} }







>
>

|












|







2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
    interp alias  {} ltx::+  {} ::uexpr::ltx::BinaryOp +


    proc eval::+CR  {a args} {
	# multiple add/subtract operations
	# a is the first (left) operand
	# args is a list of +/- uval pairs
	# always evaluates as +
	# subtraction is (-- \U229F) is handled by \U2296 and ine
	foreach {op b} $args {
	    set a [+ $a $b]
	}
	return $a
    }


    # LaTeX for a line break in an equation at an inline binary +/- operator
    proc ltx::+CR {a args} {
	# build a vertical array of subexpressions
	# using operators between subexpressions
	# append operator to the left subexpression,
	# prepend operator to the right subexpression
	# start array with first subexpression
	set res [string cat { \begin{array}{c} }  $a]
	foreach {op b} $args {
	    # continue by appending operator, line break
	    # operator (again) followed by next subexpression
	    set res [string cat $res " " $op { \\ \mbox{} } $op " "  $b]
	}
	# end array
	string cat $res	{ \end{array} }
2770
2771
2772
2773
2774
2775
2776
2777
2778

2779

2780
2781
2782
2783
2784
2785
2786
	# 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
	}
    }







<

>

>







2776
2777
2778
2779
2780
2781
2782

2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
	# parse, tclize, memoize an expression
	# memoize expressions in uexpr::exprs dict
	# Note when the resulting cmd is evaluated to a value it expects:
	#   callingLevel (context for variables)
	#   and locals (a dict of local variable values from a function call, often empty)
	# to be defined in the context of the namespace eval eval ...
	variable exprs

	variable debug
	# disable memoizing if debug is set
	if {!$debug && [dict exists $exprs $expr]} {
	    # return memorized parsed and tclized expression
	    dict get $exprs $expr
	} else {
	    set cmd [tclize [parse $expr]]
	    dict set exprs $expr $cmd
	    return $cmd
	}
    }
3049
3050
3051
3052
3053
3054
3055
3056
3057
3058
3059
3060
3061
3062
3063
3064
3065
3066
3067
3068
3069
3070
3071
3072
3073
3074
3075
3076
3077
3078
3079
3080
3081
3082
3083
3084
3085

3086
3087
3088
3089
3090
3091
3092
3093
3094
3095
3096
3097
3098
3099
3100
3101
3102
3103
	    $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 {
		lset ve 0 setName
		lset ve 1 [list uval [lindex $ve 1]]
	    }
	    arrayAccess {lset ve 0 setArrayAccess}
	    default {
		error "$varExpr is not a variable reference"
	    }
	}
	#puts "uset ve: $ve  value: $val"
	namespace eval eval [concat [tclize $ve] [list $val]]
    }



    # format expression and result with LaTeX
    # return a list of:
    #   LaTeX formatted expression







|

|


|
<
<
<

|





<
<
<
<
<
<
<
<
<

|



>
|









|







3056
3057
3058
3059
3060
3061
3062
3063
3064
3065
3066
3067
3068



3069
3070
3071
3072
3073
3074
3075









3076
3077
3078
3079
3080
3081
3082
3083
3084
3085
3086
3087
3088
3089
3090
3091
3092
3093
3094
3095
3096
3097
3098
3099
	    $callingLevel  \
	    [namespace eval eval [tclizeExpr $expr]]  \
	    [lindex $runits end]
    }



    # saves a value in a variable in the calling context
    # The variable name is formatted the same as variable references in uexpr
    # The variable can be a reference to a tcl array: a(b)
    # It can be a reference to an entry in vector or higher dimension array
    # such as: vv..1 or aa..(1,3,2) 
    # Return the value saved




    proc uset {varExpr value} {
	# evaluate an expression
	# return result as valid unit expression
	set callingLevel #[expr {[info level] -1}]
	# there are no local variables
	set locals {}









	# parse variable reference
	set ve [parse \$$varExpr]
	#puts "ve: $ve"
	# make primary variable reference a set referenced value
	switch [lindex $ve 0] {
	    name {error "oops make this a dash"}
	    varName {
		lset ve 0 setName
		lset ve 1 [list uval [lindex $ve 1]]
	    }
	    arrayAccess {lset ve 0 setArrayAccess}
	    default {
		error "$varExpr is not a variable reference"
	    }
	}
	#puts "uset ve: $ve  value: $val"
	namespace eval eval [concat [tclize $ve] [list $value]]
    }



    # format expression and result with LaTeX
    # return a list of:
    #   LaTeX formatted expression
3206
3207
3208
3209
3210
3211
3212
3213
3214
3215
3216
3217
3218

3219
3220
3221
3222
3223
3224
3225
	variable units
	lsort -dictionary [array names units $pattern]
    }
	    
	


    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
	    }







|





>







3202
3203
3204
3205
3206
3207
3208
3209
3210
3211
3212
3213
3214
3215
3216
3217
3218
3219
3220
3221
3222
	variable units
	lsort -dictionary [array names units $pattern]
    }
	    
	


    proc unitsLike {{expr 1} args} {
	# return a sorted list of all units and constants
	# with the same dimensions as the expression
	variable units
	set callingLevel [expr {[info level] - 1}]
	set locals {}
	set expr [concat $expr $args]
	set uval [namespace eval eval [tclizeExpr $expr]]
	lsort -dictionary [lmap {key value} [array get units] {
	    if {[SameUnits $uval $value]} {
		set key
	    } else {
		continue
	    }

Changes to uexpr.test.

133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
    } -match glob -returnCodes 1 \
	-result "*expects two or three arguments: stack-level variable-name ?value?*"


    test uset-1 {write to new variable} -setup {
	catch {unset a}
    } -body {
	expr {[uexpr::uset a 34*5psi=psi] == $a}
    } -result 1

    test uset-1.1 {write to existing variable} -setup {
	catch {unset a}
	set a 6
    } -body {
	expr {[uexpr::uset a 34*5psi=psi] == $a}
    } -result 1

    test uset-2 {write where Tcl array exists but membeer does not} -setup {
	catch {unset a}
	set a(1) 4
    } -body {
	expr {[uexpr::uset a.3 34*psi] == ${a.3}}
    } -result 1
    
    test uset-2.1 {write where array and member exist} -setup {
	catch {unset a}
	catch {unset a.3}
	set a(3) 4
    } -body {
	expr {[uexpr::uset a.3 34*psi] == $a(3)}
    } -result 1
        
    test uset-3 {write into new list} -setup {
	catch {unset a}
    } -body {
	expr {[uexpr::uset a..0 34] == [lindex $a 0]}
    } -result 1

    test uset-3.1 {write into new list} -setup {
	catch {unset a}
    } -body {
	expr {[uexpr::uset a..(0,0) 34psi=bar] == [lindex $a 0 0]}
    } -result 1

    test uset-3 {write into new list} -setup {
	catch {unset a}
    } -body {
	expr {[uexpr::uset a..0 34] == [lindex $a 0]}
    } -result 1

    test uset-3.1 {write into existing list} -setup {
	catch {unset a}
	set a [uexpr 1psi,2ft,3amp]
    } -body {
	expr {[uexpr::uset a..1 34*5psi=psi] == [lindex $a 1]}
    } -result 1

    test uset-4 {append to existing list} -setup {
	catch {unset a}
	set a [uexpr 1psi,2ft,3amp]
    } -body {
	expr {[uexpr::uset a..3 34*5psi=psi] == [lindex $a 3]}
    } -result 1

    test uset-5 {append to existing value, creating list} -setup {
	catch {unset a}
	set a [uexpr 1psi]
    } -body {
	expr {[uexpr::uset a..1 34*5psi=psi] == [lindex $a 1]}
    } -result 1

    test uset-6 {not a writable reference} -setup {
	set a [uexpr 1psi]
    } -body {
	uexpr::uset 3+4 34psi=bar
    } -returnCodes error  -match glob  -result "* is not a variable reference"

    test uset-6.1 {index too big} -setup {
	set a [list 1 2 ]
    } -body {
	uexpr::uset a..3 66
    } -returnCodes error  -match glob  \
	-result "cannot set list entry at index 3 in variable a"

    test uset-6.2 {bad index for list in array member} -setup {
	catch {unset a}
	catch {unset a.m}
	set a(m) [list 1 2]
    } -body {
	uexpr::uset a.m..4 34*5psi=psi
    } -returnCodes error  -match glob \
	-result {*cannot set list entry at index 4 in member m of Tcl array a*}

    test uset-6.3 {bad index for first write to list} -setup {
	catch {unset a}
    } -body {
	uexpr::uset a..4 34*5psi=psi
    } -returnCodes error  -match glob \
	-result "*cannot use index 4 for first write to list in a*"

    
    
    test ltx::num-1 {} -setup $SETUP -body {
	uexpr::ltx::num 1.0







|






|


|



|




<


|











|



















|






|





|
|










<


|

|




|







133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158

159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217

218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
    } -match glob -returnCodes 1 \
	-result "*expects two or three arguments: stack-level variable-name ?value?*"


    test uset-1 {write to new variable} -setup {
	catch {unset a}
    } -body {
	expr {[uexpr::uset a 34*5psi] == $a}
    } -result 1

    test uset-1.1 {write to existing variable} -setup {
	catch {unset a}
	set a 6
    } -body {
	expr {[uexpr::uset a 34*5psi] == $a}
    } -result 1

    test uset-2 {write where Tcl array exists but member does not} -setup {
	catch {unset a}
	set a(1) 4
    } -body {
	expr {[uexpr::uset a(3) 34*psi] == ${a(3)}}
    } -result 1
    
    test uset-2.1 {write where array and member exist} -setup {
	catch {unset a}

	set a(3) 4
    } -body {
	expr {[uexpr::uset a(3) 34*psi] == $a(3)}
    } -result 1
        
    test uset-3 {write into new list} -setup {
	catch {unset a}
    } -body {
	expr {[uexpr::uset a..0 34] == [lindex $a 0]}
    } -result 1

    test uset-3.1 {write into new list} -setup {
	catch {unset a}
    } -body {
	expr {[uexpr::uset a..(0,0) 34psi] == [lindex $a 0 0]}
    } -result 1

    test uset-3 {write into new list} -setup {
	catch {unset a}
    } -body {
	expr {[uexpr::uset a..0 34] == [lindex $a 0]}
    } -result 1

    test uset-3.1 {write into existing list} -setup {
	catch {unset a}
	set a [uexpr 1psi,2ft,3amp]
    } -body {
	expr {[uexpr::uset a..1 34*5psi=psi] == [lindex $a 1]}
    } -result 1

    test uset-4 {append to existing list} -setup {
	catch {unset a}
	set a [uexpr 1psi,2ft,3amp]
    } -body {
	expr {[uexpr::uset a..3 34*5psi] == [lindex $a 3]}
    } -result 1

    test uset-5 {append to existing value, creating list} -setup {
	catch {unset a}
	set a [uexpr 1psi]
    } -body {
	expr {[uexpr::uset a..1 34*5psi] == [lindex $a 1]}
    } -result 1

    test uset-6 {not a writable reference} -setup {
	set a [uexpr 1psi]
    } -body {
	uexpr::uset 3+4 34psi
    } -returnCodes error  -match glob  -result "* is not a valid variable name*"

    test uset-6.1 {index too big} -setup {
	set a [list 1 2 ]
    } -body {
	uexpr::uset a..3 66
    } -returnCodes error  -match glob  \
	-result "cannot set list entry at index 3 in variable a"

    test uset-6.2 {bad index for list in array member} -setup {
	catch {unset a}

	set a(m) [list 1 2]
    } -body {
	uexpr::uset a(m)..4 34*5psi
    } -returnCodes error  -match glob \
	-result {*cannot set list entry at index 4 in variable a(m)*}

    test uset-6.3 {bad index for first write to list} -setup {
	catch {unset a}
    } -body {
	uexpr::uset a..4 34*5psi
    } -returnCodes error  -match glob \
	-result "*cannot use index 4 for first write to list in a*"

    
    
    test ltx::num-1 {} -setup $SETUP -body {
	uexpr::ltx::num 1.0
396
397
398
399
400
401
402



403

404
405
406



407

408
409
410
411
412
413
414











415
416
417
418
419
420
421
	uexpr {3*$a=in}
    } -cleanup {unset a}  -result {9.0 in}

    test varName-2 {} -setup {catch {unset a}} -body {
	uexpr {3*$a=in}
    } -returnCodes error  -match glob  -result {*a is not a variable name*}




    test varName-3 {} -setup $SETUP -body {

	uexpr {3*$ft=in}
    } -returnCodes error  -match glob  -result {*ft is not a variable name*}
    



    test name {invalid name character}  -setup {catch {unset _a}} -body {

	uexpr 3+$_a
    }  -returnCodes error  -match glob  \
	-result "*no such variable*"

    test varName-1 {} -setup {set _a 3*in} -body {
	uexpr {3*$_a=in}
    } -cleanup {unset _a}  -result {9.0 in}













    test unitName-1 {} -setup $SETUP -body {
	uexpr {3*:in=in}
    } -cleanup $CLEANUP  -result {3.0 in}

    test unitName-2 {} -setup $SETUP -body {







>
>
>
|
>



>
>
>
|
>
|
|
|

|


>
>
>
>
>
>
>
>
>
>
>







394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
	uexpr {3*$a=in}
    } -cleanup {unset a}  -result {9.0 in}

    test varName-2 {} -setup {catch {unset a}} -body {
	uexpr {3*$a=in}
    } -returnCodes error  -match glob  -result {*a is not a variable name*}

    test varName-3 {tcl var ignores unit} -setup {set ft 5sec} -body {
	uexpr {3*$ft=sec}
    } -cleanup {unset ft}  -result {15.0 sec}
    
    test varName-3.1 {tcl var ignores unit} -setup {catch {unset ft}} -body {
	uexpr {3*$ft=in}
    } -returnCodes error  -match glob  -result {*ft is not a variable name*}
    
    test varName-4 {use . _ chars in Tcl var name}  -setup {set _a.b 5} -body {
	uexpr {3+$_a.b}
    }  -cleanup {unset _a.b}  -result 8

    test varName-4.1 {verify braces required}  -setup {set _a.b 5} -body {
	uexpr 3+$_a.b
    }  -cleanup {unset _a.b}  -returnCodes error  -match glob  \
	-result {*can't read "_a": no such variable*}

    test varName-5 {} -setup {set _a 3*in} -body {
	uexpr {3*$_a=in}
    } -cleanup {unset _a}  -result {9.0 in}

    test varName-6 {verify feature was removed} -setup {set a(b) 3*in} -body {
	uexpr {3*$a.b=in}
    }  -cleanup {unset a}  -returnCodes error  -match glob  \
	-result "*a.b is not a variable name*"

    test varName-6.1 {} -setup {set a(3) 3*in} -body {
	uexpr {3*$a.b=in}
    }  -cleanup {unset a}  -returnCodes error  -match glob  \
	-result "*a.b is not a variable name*"



    test unitName-1 {} -setup $SETUP -body {
	uexpr {3*:in=in}
    } -cleanup $CLEANUP  -result {3.0 in}

    test unitName-2 {} -setup $SETUP -body {
555
556
557
558
559
560
561
562
563
564
565

566
567
568
569
570
571
572
	-result "* with index*is not an array variable name*"
    
    test array-25 {} -setup $arraySetup -body {
	set vv [uexpr ((1,2,3),(11,22,33,44)),(10,20,30,50),666]
	uexpr vv..(0,1,3)
    }  -cleanup $arrayCleanup  -match real  -result 44 

    test array-26 {this shuld be an error!!} -setup $arraySetup -body {
	uexpr::uset vv ((1,2,3),(11,22,33,44)),(10,20,30,50),666
	uexpr vv..(1,2)
    }  -cleanup $arrayCleanup  -match real  -result 30 



    
    # LaTeX for arrays
    
    test ltxArray-1 {} -setup $arraySetup -body {
	uexpr::ltxExpr vv..0







|


|
>







572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
	-result "* with index*is not an array variable name*"
    
    test array-25 {} -setup $arraySetup -body {
	set vv [uexpr ((1,2,3),(11,22,33,44)),(10,20,30,50),666]
	uexpr vv..(0,1,3)
    }  -cleanup $arrayCleanup  -match real  -result 44 

    test array-26 {no value at given index} -setup $arraySetup -body {
	uexpr::uset vv ((1,2,3),(11,22,33,44)),(10,20,30,50),666
	uexpr vv..(1,2)
    }  -cleanup $arrayCleanup  -returnCodes error  -match glob  \
	-result "*array vv has no value for index 1 2*"


    
    # LaTeX for arrays
    
    test ltxArray-1 {} -setup $arraySetup -body {
	uexpr::ltxExpr vv..0
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
















1825
1826
1827
1828
1829
1830
1831
1832
1833
1834


1835



1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
    test +CR-3 {} -setup $SETUP -body {
	set a 6*psi
	uexpr 144*lbf/ft^2 ++ a = psi
    } -cleanup $CLEANUP -result {7.0 psi}

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

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


    # test the ++- cases
    
    test +CR-7 {} -setup $SETUP -body {
	lindex [uexpr::ltxExpr 4++-5] 0
    } -cleanup $CLEANUP -result { \begin{array}{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 {
	lindex [uexpr::ltxExpr 4--5] 0
    } -cleanup $CLEANUP -result { \begin{array}{l} 4 - \\ \mbox{} - 5 \end{array} }

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




    test subtract {} -setup $SETUP -body {
	uexpr 4 - 5
    } -cleanup $CLEANUP -result -1







|



|



|






|



|





|



|


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










>
>
|
>
>
>
|






|








|



|



|







1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
    test +CR-3 {} -setup $SETUP -body {
	set a 6*psi
	uexpr 144*lbf/ft^2 ++ a = psi
    } -cleanup $CLEANUP -result {7.0 psi}

    test +CR-4 {} -setup $SETUP -body {
	lindex [uexpr::ltxExpr 4++5] 0
    } -cleanup $CLEANUP -result { \begin{array}{c} 4 + \\ \mbox{} + 5 \end{array} }

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

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


    # test the ++- cases
    
    test +CR-7 {} -setup $SETUP -body {
	lindex [uexpr::ltxExpr 4++-5] 0
    } -cleanup $CLEANUP -result { \begin{array}{c} 4 + \\ \mbox{} + -5 \end{array} }

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

    # test multiple ++ -- cases
    
    test +CR-9 {} -setup $SETUP -body {
	lindex [uexpr::ltxExpr 4++5++6] 0
    } -cleanup $CLEANUP -result { \begin{array}{c} 4 + \\ \mbox{} + 5 + \\ \mbox{} + 6 \end{array} }

    test +CR-10 {} -setup $SETUP -body {
	lindex [uexpr::ltxExpr 4++5--6] 0
    } -cleanup $CLEANUP -result { \begin{array}{c} 4 + \\ \mbox{} + 5 - \\ \mbox{} - 6 \end{array} }

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

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

    test +CR-13 {} -setup $SETUP -body {
	uexpr 2*3 - 4 ++ 5 - 6 ++ 7 - 8 -- 9 + 5*3
    } -cleanup $CLEANUP -result 6

    test +CR-14 {} -setup $SETUP -body {
	uexpr 2*3 - 4 ++ 5 - 6 ++ 7 - 8 -- 9*2 + 5*3
    } -cleanup $CLEANUP -result -3

    test +CR-15 {} -setup $SETUP -body {
	uexpr 2*3 - 4 ++ 5 - 6 ++ 7 - 8 -- 3^2 + 5*3
    } -cleanup $CLEANUP -result 6.0

    
    
    # line breaking subtract
    test -CR {} -setup $SETUP -body {
	uexpr 4 -- 5
    } -cleanup $CLEANUP -result -1

    test -CR-1 {} -setup $SETUP -body {
	set a 6
	uexpr 2 - 3 + 4 -- a + 5 + 7
    } -cleanup $CLEANUP -result 9

    test -CR-1.1 {} -setup $SETUP -body {
	set a 6
	uexpr 2 + 3 - 4 -- a - 5 + 7
    } -cleanup $CLEANUP -result -3

    # the error message is not ideal, but probably good enough
    test -CR-2 {} -setup $SETUP -body {
	set a 6*psi
	uexpr 4 -- a
    } -cleanup $CLEANUP  -returnCodes error  -match glob  \
	-result {*trying to add values with different units*}

    test -CR-3 {} -setup $SETUP -body {
	set a 6*psi
	uexpr 144*lbf/ft^2 -- a = psi
    } -cleanup $CLEANUP -result {-5.0 psi}

    test -CR-4 {} -setup $SETUP -body {
	lindex [uexpr::ltxExpr 4--5] 0
    } -cleanup $CLEANUP -result { \begin{array}{c} 4 - \\ \mbox{} - 5 \end{array} }

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

    test -CR-6 {} -setup $SETUP -body {
	lindex [uexpr::ltxExpr 3*(4--5)] 0
    } -cleanup $CLEANUP -result {3\cdot  \left (  \begin{array}{c} 4 - \\ \mbox{} - 5 \end{array}  \right ) }




    test subtract {} -setup $SETUP -body {
	uexpr 4 - 5
    } -cleanup $CLEANUP -result -1
2449
2450
2451
2452
2453
2454
2455





2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468



    test unitsLike-1 {} -setup $SETUP  -body {
	expr {"psi" in [uexpr::unitsLike bar]}
    } -result 1







    
    test unitNames-1 {} -setup $SETUP  -body {
	uexpr::unitNames bar
    } -result bar


    

    cleanupTests
}

#namespace delete ::uexpr::test







>
>
>
>
>













2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512



    test unitsLike-1 {} -setup $SETUP  -body {
	expr {"psi" in [uexpr::unitsLike bar]}
    } -result 1


    test unitsLike-2 {} -setup $SETUP  -body {
	expr {"watt" in [uexpr::unitsLike BTU / hr]}
    } -result 1


    
    test unitNames-1 {} -setup $SETUP  -body {
	uexpr::unitNames bar
    } -result bar


    

    cleanupTests
}

#namespace delete ::uexpr::test