Boost logo

Boost :

From: joel de guzman (djowel_at_[hidden])
Date: 2002-03-20 09:21:26


Hi,

FYI, you can find a LL alternative here:

preliminary draft docs:

    http://spirit.sourceforge.net/dl_more/phoenix_manual.txt

v0.9 alpha:

    http://spirit.sourceforge.net/dl_more/phoenix_v0_9.zip

Phoenix is an FP library that is quite new. It is part of the Spirit
parser library distribution but does not rely on Spirit and can stand
alone on its own. Phoenix was originally conceived as Spirit's
semantic-expression sub-framework. Phoenix is very new, not quite as
matured as LL. In fact it is only one month old (make that 3 if you
include the prototype Spirit::SEs).

Why reinvent the wheel?

Because LL did not fit Spirit's requirements. For many months now, I
was searching for a semantic expression package. Unfortunately, I was
taken aback because Spirit needed *true* closure support and local
variables which LL and FC++, the most viable candidates lack. True
closures means access to true stack variables. This is a requirement
because Spirit is highly recursive.

Don't get me wrong. I am not positioning Phoenix as a replacement to
LL. As I mentioned, Phoenix is not quite as mature. Phoenix has some
features that some might find interesting though. Phoenix is
influenced by both FC++ and LL. It has rank-2 polymorphic functoids
(FC++ jargon) along with LL's lambda expressions and statements.

Some highlights:

1) Highly modular and extensible. The framework is built from a few
simple axioms. The rank-2 polymorphic functor is the main building
block where all modules are built from. The framework is organized as:

                     +-----------+
                     | binders |
                     +-----------+-----------+------------+
                     | functions | operators | statements |
        +------------+-----------+-----------+------------+
        | primitives | composite |
        +------------+------------------------------------+
        | actor |
        +-------------------------------------------------+
        | tuples |
        +-------------------------------------------------+

The framework's architecture is completely orthogonal. The
relationship between the layers is totally acyclic. Lower layers do
not depend nor know the existence of higher layers. Modules in a layer
do not depend on other modules in the same layer. This means for
example that the client can completely discard binders if she does not
need it; or perhaps take out lazy-operators and lazy-statements and
just use lazy-functions, which is desireable in a pure FP application.

In fact, the layers above primitives and composite can be regarded as
extensions. It is highly extensible. For instance, I've written a
functionality that allows true local (hardware stack) variables in
less than 2 hours. The nested closure support needed by Spirit was
written in half a day (including design). The framework is designed to
be extended.

2) Compiles on more compilers.

Right now, it has been tested on

    1)G++2.95
    2)G++3.0
    3)Comeau
    4)Intel
    5)Borland 5.5.1 { This is not a typo :-}

I'll be bold: it can and will be ported to MSVC. The framework uses
the techniques used in the implementation of the Spirit framework.
Right now, Spirit has been ported twice in a row to MSVC. First by
Bruce Florman V1.1, now by Raghav Satish V1.3.

3) Non-strict maximum arity. Another requirement of Spirit is that a
semantic action framework should be adaptable. A function/functor can
and should be able to ignore extra parameters. Thus, I can plug in
an action that expects less arguments. Example:

    (arg1 + arg2)(a, b, c); // c is ignored.

Minimum arity is always strict:

    (arg1 + arg2)(a); // compile error

Taking this further, in-between arguments may even be ignored:

    add(arg1, arg5)(a, b, c, d, e)

In C and C++, a function can have extra arguments that are not at
all used by the function body itself. For instance, call-back
functions may provide more information than is actually needed at
once. These extra arguments are just ignored.

    <<< Note: There are a few reasons why enforcing strict arity is
    not desireable. A case in point is the callback function. Typical
    callback functions provide more information than is actually
    needed. Lambda functions are often used as callbacks. >>>

4) Everything is an 'actor'. The placeholders, the variables, the
values are all actors. This means that arg1, var(x), val(x) etc
have identities:

    cout << arg1(a); // print a
    cout << var(a)(); // print a
    cout << val(a)(); // print a
    cout << arg2(a, b); // print b

5) Rank-2 polymorphic actors. Example:

struct is_odd_ {

    template <typename ArgT>
    struct result { typedef bool type; };

    template <typename ArgT>
    bool operator()(ArgT arg1) const
    { return arg1 % 2 == 1; }
};

function<is_odd_> is_odd;

    // Find the first odd number in container c
    iterator it = find_if(c.begin(), c.end(), is_odd(arg1));

Polymorphic actors are so powerful. The entire framework is based on
this. The whole STL algorithms library can be made lazy through this
alone. Of course, being first class citizens, they can mix and match
with lazy-operators and statements:

    // Print all odd contents of an stl container c
    for_each(c.begin(), c.end(),
        if_(is_odd(arg1))
        [
            cout << arg1 << ' '
        ]
    );

Notice the if_ syntax.

6) Configurable maximum arity from 3~15 arguments/tuple elements. This
is also a Spirit requirement. Fixing the maximum limit to 10 or 9
makes for ---loooonnnng--- compile times. The Spirit parser already
pushes the limit. For example, some huge grammars with full semantic
expressions take ages to compile. In most cases, especially with
semantic actions, arity==3 is sufficient. If not, a #define
PHOENIX_LIMIT can be set from 3~15.

-------------------------------------------

So there, bash it, critique it. In the meantime, I will duck :-)

Regards,
--Joel de Guzman


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk