Re: dynamic precedence, was Dangling else

Chris F Clark <cfc@shell01.TheWorld.com>
12 Mar 2006 14:00:12 -0500

          From comp.compilers

Related articles
Re: Dangling else wyrmwif@tsoft.org (SM Ryan) (2006-02-24)
Re: Dangling else rsc@swtch.com (Russ Cox) (2006-02-24)
Re: Dangling else Brian.Inglis@SystematicSW.ab.ca (Brian Inglis) (2006-03-11)
Re: dynamic precedence, was Dangling else cfc@shell01.TheWorld.com (Chris F Clark) (2006-03-12)
Re: dynamic precedence, was Dangling else alexc@TheWorld.com (Alex Colvin) (2006-03-14)
Re: dynamic precedence, was Dangling else torbenm@app-4.diku.dk (2006-03-14)
Re: dynamic precedence, was Dangling else dot@dotat.at (Tony Finch) (2006-03-15)
| List of all articles for this month |
From: Chris F Clark <cfc@shell01.TheWorld.com>
Newsgroups: comp.compilers
Date: 12 Mar 2006 14:00:12 -0500
Organization: The World Public Access UNIX, Brookline, MA
References: 06-02-154 06-02-168 06-03-024
Keywords: parse
Posted-Date: 12 Mar 2006 14:00:12 EST

Brian Inglis wrote:
> I can't think of a language that allows specified or modified
> precedence, other than Algol 68, which required a priority on operator
> definitions; but 2 level vanWijngaarden grammars do not seem to be in
> use now.


You don't need VW grammars to allow specifying operator precedence at
compile time (i.e. allowing the program to change its own precedence).
There are many precedence parsing techniques where one can read in /
modify the tables. In fact, it is quite simple to parse with a
numeric list of operator priorities that the user can edit. I
remember knowing that technique back in '78 when I incorporated it
into a language that I designed (and perhaps fortunately never
implemented).


I can't remember which languages (besides A68) allowed one to modify
the precedence of operators, however there must have been a few,
especially if one includes the "extensible languages" that were once
popular and are now mostly forgotten. I believe our moderator refers
to such languages as write only.


However, from a software engineering perspective, allowing users to
reassign ("fix") the precedences does not simplify the problem. It
makes it worse. Now, when writing an expression, one has to know
whether the default rules apply or some "fixed" set. Thus, when
writing defensive code, one has to protect against not only the
follies of the original language designer, but also the follies of the
other authors of the current application one is working on. The
result is that one cannot trust that the code one is looking at does
what one expects, unless one knows the context.


I see the same difficulty (although from a different source) in C++
code. One recent example was from the SystemC library written in C++
that my current Project Leader is investigating. Here, recall the
comment library design is language design. The user writes code to
create object in that language/library in the following form:


myflipflop ff1("ff2");
    // where myflipflop is some user defined class in the SystemC framework
    // ff1 is the name to use for the object in C++
    // ff2 is the name to use for the object in SystemC


Now, myflipflop will be defined using SystemC macros and templates.
The interesting part is that there is no constructor for a myflipflop
class that takes a const char * (or even a char *) argument. In fact,
there is only one constructor in a myflipflop, and it takes a special
SystemC class object as an argument. This SystemC class is a class
that controls object allocation. The code works, because there is a
constructor in that class, that takes a const char * argument, and the
compile infers that one wants to use that constructor to convert the
original const char * "ff2" into an appropriate argument. The C++
compiler creates a temporary object of the SystemC class using the
constructor and uses that to call the constructor for the user
myflipflop class.


All smoke and mirrors. The result was that my project leader couldn't
understand what was happening, and was looking for operator char * in
a variety of classes (which would have been conversions in the wrong
direction) etc. It even took me a few minutes to recall which C++ rule
applied and why the code worked as it did. This is not the generally
desired result, unless one wants a write only language.


Now, I don't think the fault lies in the ability in C++ to use a
constructor as a cast. The problem lies in not having tools that
unravel such "tricks" and make such things explicit. For example, if
the problem had been a tricky C preprocessor directive, which is one
of the things we suspected first, the solution is simple: run the
preprocessor stand-alone and view the output file.


For such reason, I really fear when my project leader decides to
investigate something using the boost library and attempts to do
functional programming in C++. I don't know if I will be able to
figure out the underlying semantics. Functional semantics themselves
have their own simplicity. However, I don't know how well they layer
on top of C++.


Returning back to precedence, I see similar effects. Most coders I
know tend to code somewhat defensively with respect to operator
precedence, adding extra levels of parenthesis to correct not only the
flaws in the current language, but the flaws in all languages that one
has ever used. I see it a lot in expressions which mix logical,
relational, and bitwise operators. I don't know which parenthesis are
redundant in the expression below, and I don't care. I don't even know
which language made me suspcicious of the priorities of the operators
involved, and again I don't care. What I do know is that my intent of
which operators should be applied in which order has been made
explicit. Curiously, after enough years of having written code that
way, I no longer see the parenthesis as redundant, even though the
code below expresses my expectation for priorities. So, while it
might be clearer to remove some of the parenthesis in the code shown,
I have gotten used to the explicitness and can only think of one pair
that I would like to remove.


if ((((a & 0xff) == 0x3f) && (((a & 0x3f) | 0x1f) < 0xff)) || (a == 0x3f))


Summary: adding compile-time operator precedence is actually
easy. Doing so is not necessarily wise. What we really need is ways of
taking the "shortcuts" out of languages and making such shortcuts
explicit. When we have that, adding user configurable operator
precedence and other shortcuts may be beneficial, because the reader
can take them back out and get back to a simpler (if less terse)
language where the steps are more explicit. One won't do that
everywhere, just in the cases where the interactions are unclear.


Perhaps, I'm just becoming reactionary.


Hope this helps,
-Chris


*****************************************************************************
Chris Clark Internet : compres@world.std.com
Compiler Resources, Inc. Web Site : http://world.std.com/~compres
23 Bailey Rd voice : (508) 435-5016
Berlin, MA 01503 USA fax : (978) 838-0263 (24 hours)
------------------------------------------------------------------------------



Post a followup to this message

Return to the comp.compilers page.
Search the comp.compilers archives again.