Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Safe Units Handling in Ada

1 view
Skip to first unread message

Fraser Wilson

unread,
May 21, 2002, 6:01:49 AM5/21/02
to

After a recent discussion, I put together Macks, a small program that
generates Ada packages for dealing with physical units in a type safe
way.

You can now download Version 0.0.1 (that's confidence) from

http://blancolioni.org/ada/macks/macks-0.0.1-src.tar.gz
http://blancolioni.org/ada/macks/macks-0.0.1-src.zip

Instructions are in the README, but if you have GNAT, then all you have
to do is this:

gnatmake -o macks macks-driver

The tar.gz version is just the zipped version run through dos2unix. I
don't currently have a Unix box with an Ada compiler installed (it's a
depressing story), so technically that version hasn't been tested
properly (except by (a) checking for the absence of ^M on the Unix box
I do have, and (b) downloading to the Windows machine and compiling it).

cheers,
Fraser.

Pat Rogers

unread,
May 21, 2002, 10:21:31 AM5/21/02
to
"Fraser Wilson" <newsf...@blancolioni.org> wrote in message
news:u3cwlb...@FWILSON.i-did-not-set--mail-host-address--so-shoot-me...

>
> After a recent discussion, I put together Macks, a small program that
> generates Ada packages for dealing with physical units in a type safe
> way.


Sounds like a good article for Ada Letters! Hint, hint, hint...

--
Pat Rogers
Technical Editor, Ada Letters
pro...@classwide.com


Stephen Leake

unread,
May 21, 2002, 11:58:33 AM5/21/02
to
Fraser Wilson <newsf...@blancolioni.org> writes:

> After a recent discussion, I put together Macks, a small program that
> generates Ada packages for dealing with physical units in a type safe
> way.

I've downloaded it and read the README and example.macks, and compiled
everything; looks good!

One quibble; example.macks says "Energy is Force / Second;". In the
real world, there is no simple relationship among Energy, Force, and
Second. Energy is Joules = kg m^2 / s^2; Force is Newtons = kg m /
s^2.

You can have "Energy is Kilogram * Square_Velocity or Force * Meter;".

(And yes, I did actually get out my physics text to check this :).

Also, is there a description of the grammar allowed in .macks files? I
assume I could work it out by reading the Ada code, but a
user-readable description would be useful. Hmm, looks like the only
thing missing from example.macks is "or", and if you use my definition
of Energy above, you get that (assuming I used "or" correctly). So
maybe that's enough; just add some comments to example.macks.

Looking at the output of "macks example.macks", I'd like a way to put
in a copyright notice; maybe have macks copy the first contiguous
comment in the source file?

Did you try using "pragma Import (Intrinsic, "*")", instead of
providing a body? That sounds like a good idea to me. Looking at the
assembly code generated by

gnatmake -O3 -gnatn example_success -cargs -save-temps

there are no function calls for "*", so pragma Inline is enough. But
'pragma Import (Intrinsic)' seems more elegant, and may save
compilation time, which may or may not be an issue.


In example_fail.adb, the statements

N := Kg * MpS / S;
N := Kg * M / S / S;

can be made to compile by adding parens:

N := Kg * (MpS / S);
N := Kg * ((M / S) / S);

I agree it would be nice if the parens were not necessary, but I don't
mind them.


Finally, what is the derivation of the word "Macks"?

--
-- Stephe

Fraser

unread,
May 22, 2002, 4:32:05 AM5/22/02
to
Stephen Leake wrote:

> I've downloaded it and read the README and example.macks, and compiled
> everything; looks good!

Glad to hear it.

> One quibble; example.macks says "Energy is Force / Second;". In the
> real world, there is no simple relationship among Energy, Force, and
> Second. Energy is Joules = kg m^2 / s^2; Force is Newtons = kg m /
> s^2.

Whoops. What's Force / Second mean? That should be Force * Meter I
guess, and that evaluates to Joules directly. I wonder if there's a
clean way of handling constants, such as the 0.5 in the kinetic energy
formula.

> (And yes, I did actually get out my physics text to check this :).

I googled the basics, and did the derived from memory. The physics
doesn't change, but the memory gets a bit 2Fe + 3O2 => 2Fe2O3. :)

> Also, is there a description of the grammar allowed in .macks files?

Oh, good idea. It's not at all complicated, but I'll add a description.
Here's a quick version:

macks-file ::= { unit-specification ';' }

unit-specification ::=
fundamental-unit-specification | derived-unit-specification

fundamental-unit-specification ::= unit-name [ 'is' 'fundamental' ]

derived-unit-specification ::=
unit-name 'is' dimension-expression
{ 'or' dimension-expression }

dimension-expression ::=
unit-name operator unit-name

opereator ::= '*' | '/'

> Looking at the output of "macks example.macks", I'd like a way to put
> in a copyright notice; maybe have macks copy the first contiguous
> comment in the source file?

Yes, that would be useful. Comments are the same as Ada, by the way. I
can't remember off-hand whether it's easy to extract comment text from
the lexer; if not, I'll come up with some other mechanism.

I agree about Intrinsic being more elegant, though I haven't tried it
yet. If it works properly (and I'm sure it will), I'll switch over, or
at least add an option.

> In example_fail.adb, the statements
>
> N := Kg * MpS / S;
> N := Kg * M / S / S;
>
> can be made to compile by adding parens:

Indeed. It's unfortunate that one has to remember which units have been
explicitly defined in order to place the parentheses properly, but I
can't think of a clean way around that right now, and at least it's
still type safe.

> Finally, what is the derivation of the word "Macks"?

(Meters/Moles), Amperes, Candela, (Kilograms/Kelvin), Seconds. I
probably spent too much time thinking about that. :-)

Thanks for the comments!

cheers,
Fraser.

Fraser

unread,
May 22, 2002, 6:21:24 AM5/22/02
to
Fraser Wilson wrote:
> After a recent discussion, I put together Macks, a small program that
> generates Ada packages for dealing with physical units in a type safe
> way.

Ah, the best-laid plans. The 'or' feature doesn't work, and the logic
was sloppy. So here's a new version which fixes that.

http://blancolioni.org/ada/macks/macks-0.0.2-src.tar.gz
http://blancolioni.org/ada/macks/macks-0.0.2-src.zip

Or you can download the changes only:

http://blancolioni.org/ada/macks/macks-0.0.2-patch.tar.gz
http://blancolioni.org/ada/macks/macks-0.0.2-patch.zip

The patches (they're not really patches in the 'diff -c' style; sorry
about that) uncompress into a directory called macks-0.0.2-patch; you
should copy the contents into your macks source directory and recompile.

I've also added a file called macks.grammar, which specifies the grammar
of the .macks file.

For some reason I can't open the .tar.gz files with WinZip. They should
work with Unix tar/gzip.

cheers,
Fraser.

Stephen Leake

unread,
May 22, 2002, 12:37:30 PM5/22/02
to
Fraser <newsf...@blancolioni.org> writes:

> Ah, the best-laid plans. The 'or' feature doesn't work, and the logic
> was sloppy. So here's a new version which fixes that.
>
> http://blancolioni.org/ada/macks/macks-0.0.2-src.tar.gz
> http://blancolioni.org/ada/macks/macks-0.0.2-src.zip

Ok, I'll give that a try.

I've done some thinking about how this will integrate with my Robotics
code, and I'll need Cartesian vectors with units. I currently have:

type Cart_Index_Type is (x, y, z);
type Cart_Vector_Type is array (Cart_Index_Type) of Real_Type;

function "*" (Left, Right : in Cart_Vector_Type) return
Cart_Vector_Type;

etc.

Now I'll have to have Meters_Cart_Vector_Type and
Velocity_Cart_Vector_Type, etc, with associated operations. Time will
remain a scalar :).

Any thoughts on extending macks to output that?

I like the origin of "macks"; it warms my physics nerd heart :). Be
sure to put it in the package somewhere.

--
-- Stephe

William C. Brennan

unread,
May 22, 2002, 6:44:13 PM5/22/02
to
On Wed, 22 May 2002 6:21:24 -0400, Fraser wrote
(in message <3CEB7124...@blancolioni.org>):

> Fraser Wilson wrote:
>> After a recent discussion, I put together Macks, a small program that
>> generates Ada packages for dealing with physical units in a type safe
>> way.
>

This units-generator package looks terrific. I've been doing this stuff by hand
for too long.

Just one comment...

The generated example.ads yields:
...
function "*"
(Left : Kilogram;
Right : Kilogram) -- This is good, we want it abstract.
return Kilogram
is abstract;


function "/"
(Left : Kilogram; -- We want this one abstract too.
Right : Kilogram)
return Kilogram
is abstract;

Wouldn't it be also useful to define the operator:

function "/"
(Left : Kilogram;
Right : Kilogram)
return Float_Type;

(where Float_Type is used throughout as a unitless scalar)?

--
Bill Brennan
bre...@netaxs.com

Russ

unread,
May 23, 2002, 4:55:47 AM5/23/02
to
William C. Brennan <bre...@netaxs.com> wrote in message news:<01HW.B91197820...@netnews.netaxs.com>...

> On Wed, 22 May 2002 6:21:24 -0400, Fraser wrote
> (in message <3CEB7124...@blancolioni.org>):

<cut>

> function "/"
> (Left : Kilogram; -- We want this one abstract too.
> Right : Kilogram)
> return Kilogram
> is abstract;
>
> Wouldn't it be also useful to define the operator:
>
> function "/"
> (Left : Kilogram;
> Right : Kilogram)
> return Float_Type;
>
> (where Float_Type is used throughout as a unitless scalar)?

Absolutely. Dividing kg by kg should obviously yield a scalar.

Russ

unread,
May 23, 2002, 5:04:26 AM5/23/02
to
Fraser Wilson <newsf...@blancolioni.org> wrote in message news:<u3cwlb...@FWILSON.i-did-not-set--mail-host-address--so-shoot-me>...

I downloaded this, and it contains a large number of files. Perhaps you could
explain how it works and give a simple example.

Fraser

unread,
May 23, 2002, 5:28:49 AM5/23/02
to
William C. Brennan wrote:

> Wouldn't it be also useful to define the operator:
>
> function "/"
> (Left : Kilogram;
> Right : Kilogram)
> return Float_Type;
>
> (where Float_Type is used throughout as a unitless scalar)?

Good idea. Even better, a one liner. It's added, and if you don't want
to wait, you can add in the procedure Write_Unit after line 83 of the
file macks-writers.adb in version 0.0.2 the following line:

Write_Function (Macks.Table.Divide, Name, Name, "Float_Type");

cheers,
Fraser.

Fraser Wilson

unread,
May 23, 2002, 6:01:25 AM5/23/02
to
Russ wrote:

> I downloaded this, and it contains a large number of files.

Yes, this is a problem. That's mainly because none of you have WL, GCS
or Sourcegen installed (but since I've never released them, you don't
have to feel bad).

> Perhaps you could
> explain how it works and give a simple example.

Yes, that's a good idea. The important bits as far as units are
concerned is under the Macks package hierarchy. Macks.Parser reads a
source file which describes the system, and Macks.Writer emits Ada
source code. Macks.Table is a simple symbol table.

Each unit type in the Macks source file has a number of operator
functions generated for it. Common to all units are the following:

function "*" (Left, Right : Unit) return Unit is abstract;
function "/" (Left, Right : Unit) return Unit is abstract;
function "*" (Left : Float_Type; Right : Unit) return Unit;
function "*" (Left : Unit; Right : Float_Type) return Unit;
function "/" (Left : Unit; Right : Float_Type) return Unit;
function "/" (Left : Unit; Right : Unit) return Float_Type;

Derived units have in addition operators corresponding to their
definition or definitions in the .macks file; for example, if

Speed is Meter / Second;

then the following operators are generated:

function "/" (Left : Meter; Right : Second) return Speed;
function "*" (Left : Speed; Right : Second) return Meter;
function "*" (Left : Second; Right : Speed) return Meter;
function "/" (Left : Meter; Right : Speed) return Second;

The file example.macks contains a simple Macks specification, which only
deals with a few derived units. However, that's enough to create an 800
line Ada package spec (which is what motivated this sort of thing in the
first place).

Macks.Driver is the main procedure; with Gnat you can use the command
gnatmake -o macks macks-driver.adb
to get an executable; this can be invoked with
macks example.macks
for example.

<filename>.macks generates <filename>.ads and <filename>.adb. To use
the generated files, with and use/use type them, then declare your
quantities and act natural. If operations that should work don't, let
me know.

I hope this helps; more thorough documentation is expected shortly.

cheers,
Fraser.

martin.m.dowie

unread,
May 23, 2002, 6:03:46 AM5/23/02
to
"Fraser" <newsf...@blancolioni.org> wrote in message
news:3CECB651...@blancolioni.org...
[snip]

> Good idea. Even better, a one liner. It's added, and if you don't want
> to wait, you can add in the procedure Write_Unit after line 83 of the
> file macks-writers.adb in version 0.0.2 the following line:
>
> Write_Function (Macks.Table.Divide, Name, Name, "Float_Type");

Wouldn't a better (by this I mean more readable/understandable) solution be
to derive a new 'Unitless' (or 'Dimensionless') type from 'Float_Type'?

Also, can't there just be a single "pragma Inline"? i.e. "pragma Inline
("*", "/");"
at the bottom of the file - doesn't this cover all the overloaded operators
prior
to this single pragma?

Cracking effort all the same! :-)

Fraser

unread,
May 23, 2002, 6:16:51 AM5/23/02
to
Stephen Leake wrote:

> I've done some thinking about how this will integrate with my
> Robotics code, and I'll need Cartesian vectors with units. I
> currently have:

[code snipped]

> Now I'll have to have Meters_Cart_Vector_Type and
> Velocity_Cart_Vector_Type, etc, with associated operations. Time will
> remain a scalar :).
>
> Any thoughts on extending macks to output that?

Someone will have to remind me how the cross product is calculated!

This is just off the top of my head:

unit Meters_Type;
unit Seconds_Type;
unit Velocity_Type is Meters_Type / Seconds_Type;

vector Meters_Cart_Vector_Type is Meters_Type * 3;
vector Velocity_Cart_Vector_Type is Velocity_Type * 3;

There would have to be some sort of syntax for specifying the vector
index type (or to ask for a record type instead). The operators + and -
(binary and unary) would be provided, as well as a cross product if it's
a 3 vector, and a dot product in any case.

Maybe we don't need a vector keyword, since it's implied by the syntax
"identifier operator constant-integer", but on the other hand there's no
need to worry about keyword bloat in a language of this size.

> I like the origin of "macks"; it warms my physics nerd heart :). Be
> sure to put it in the package somewhere.

Thanks! I was pretty tickled when I realised the letters spelled a word
(sort of).

Fraser.

Fraser

unread,
May 23, 2002, 6:24:09 AM5/23/02
to
martin.m.dowie wrote:

> Wouldn't a better (by this I mean more readable/understandable) solution be
> to derive a new 'Unitless' (or 'Dimensionless') type from 'Float_Type'?

It depends on whether you see the generic argument as an actual type
intended to be used with the definitions of the generated units package,
or whether it's merely a way of saying how precise you want the package
to be. I've opted for the former; I can make that an option.

> Also, can't there just be a single "pragma Inline"? i.e. "pragma Inline
> ("*", "/");"
> at the bottom of the file - doesn't this cover all the overloaded operators
> prior
> to this single pragma?

Um, yeah. I've vaguely wondered about this for years; but I never got
around to reading 6.3.2(7) -- silly me. I'll fix that now.

> Cracking effort all the same! :-)

Thanks!

Fraser.

martin.m.dowie

unread,
May 23, 2002, 7:03:29 AM5/23/02
to
"Fraser" <newsf...@blancolioni.org> wrote in message
news:3CECC349...@blancolioni.org...

> It depends on whether you see the generic argument as an actual type
> intended to be used with the definitions of the generated units package,
> or whether it's merely a way of saying how precise you want the package
> to be. I've opted for the former; I can make that an option.

In this particular, case I would argue for the later as this is just another
'Unit' - in
this case it just happens to be unit-less! You also get the former thrown in
for free anyway :-)


Dan Andreatta

unread,
May 23, 2002, 11:57:16 AM5/23/02
to
> The generated example.ads yields:
> ...
> function "*"
> (Left : Kilogram;
> Right : Kilogram) -- This is good, we want it abstract.
> return Kilogram
> is abstract;
>

This also should return kg**2.

Dan

Stephen Leake

unread,
May 23, 2002, 12:03:41 PM5/23/02
to
Fraser <newsf...@blancolioni.org> writes:

> Someone will have to remind me how the cross product is calculated!

I've got a generic vector package that does everything I need. It's in
SAL at
http://users.erols.com/leakstan/Stephe/Ada/Sal_Packages/index.htm,
file sal-gen_math-gen_vector.ads.

It's instantiated in files sal-gen_math-gen_dof_3.ads and
sal-gen_math-gen_dof_6.ads.

That's what I need adapted to units.

> This is just off the top of my head:
>
> unit Meters_Type;
> unit Seconds_Type;
> unit Velocity_Type is Meters_Type / Seconds_Type;
>
> vector Meters_Cart_Vector_Type is Meters_Type * 3;
> vector Velocity_Cart_Vector_Type is Velocity_Type * 3;
>

This looks promising. I think it should be in a second input file, and
produce a second package; some parts of the system need vectors, some
don't.

> There would have to be some sort of syntax for specifying the vector
> index type (or to ask for a record type instead).

Yes; I use an enumeration type for some vectors, integers for others.

> The operators + and - (binary and unary) would be provided, as well
> as a cross product if it's a 3 vector, and a dot product in any
> case.

And lots more in my package.

> Maybe we don't need a vector keyword, since it's implied by the
> syntax "identifier operator constant-integer", but on the other hand
> there's no need to worry about keyword bloat in a language of this
> size.

Yes, make it easy to parse, and easy to generate friendly error
messages.

--
-- Stephe

Fraser Wilson

unread,
May 23, 2002, 12:15:13 PM5/23/02
to
Dan Andreatta wrote:

> This also should return kg**2.

It will, but you have to ask for it (because generating all possible
unit combinations is infeasible and overkill).

For example, we can use the following definitions:

Kilogram is fundamental;
Square_Kilogram is Kilogram * Kilogram;

Macks will generate an Ada package containing, amonst other things, this
function:

function "*"
(Left : Kilogram;
Right : Kilogram)

return Square_Kilogram;

Fraser.

martin.m.dowie

unread,
May 23, 2002, 12:48:06 PM5/23/02
to
"Dan Andreatta" <andr...@mail.chem.sc.edu> wrote in message
news:338040f8.02052...@posting.google.com...

Can you set this up as another rule?

My own attempt at a Unit Generator package was allowed
users to define:
a) which of the SI base units they wanted
b) which of the SI derived units they wanted
c) to define alias (ie subtypes, e.g. "subtype Area is Metre_Squared;")
of any unit
d) to define a range of powers

This lead to a package such as:

generic
type A_Float is digits <>;
package SI_Units is

type Unitless is new A_Float;

type Metre is new A_Float;
type Metre_Squared is new A_Float;
type Metre_Cubed is new A_Float;

function "*" (L : Metre; R : Metre) return Metre_Squared;
...
end SI_Units;

The problem I found with this approach was one of line numbers when
all units are requested for the range **-3 .. **+3. Some code browsers
don't seem to like file >1200 lines long!


0 new messages