bug-bison
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: beta testing


From: Hans Aberg
Subject: Re: beta testing
Date: Wed, 21 Feb 2001 21:10:02 +0100

At 15:03 +0100 2001/02/21, Philippe Bekaert wrote:
>> If you like the unions, use them, but there is no requirement to use them.
>> (I think the point with unions is that one can get a type check, and ensure
>> that the YYSTYPE data becomes smaller.) If you write in a header that is
>> included from your .y file
>>   class my_type {
>>   ...
>>   };
>>   #define YYSTYPE my_type
>> that will be updated whenever you change a field in the class. Or what do
>> you mean?
>If I change the %union declaration in my .y file, I want all my source
>files to be recompiled with the proper, correspondingly updated YYSTYPE
>union that bison generates.
>
>Your point is that I should omit the %union declaration in the .y file,
>or did I get this wrong? Omitting the %union declaration is something
>that I don't want to do.

Either use the %union, and don't define YYSTYPE, or don't use %union, and
declare YYSTYPE if it is not going to be merely an int. Whatever you think
is best for you.

I do not use the %union command, because I decided to build a hierarchy

class object_root {
  mutable unsigned count_;
public:
  object_root() : count_(1) { }
  virtual ~object_root() { }

  object_root* copy() const { ++count_; return
const_cast<object_root*>(this); }
  void shed() { if (--count_ == 0)  delete this; }

  virtual object_root* clone() const = 0;
  virtual std::string write(Find* = 0) const = 0;
};


class object {
  object_root* data_;
  object_root* copy() const { return (data_ == 0)? 0 : data_->copy(); }
  void shed() { if (data_ != 0)  data_->shed(); }

public:
  object() : data_(0) { }
  ~object() { shed(); }

  object(const object& x) : data_(x.copy()) { }
  object& operator=(const object& x) {
    if (data_ != x.data_) { shed(); data_ = x.copy(); }
    return *this;
  }

  object(object_root* rp) : data_(rp) { }

  object_root* data() { return data_; }

  std::string write(Find* f = 0) const {
    if (data_ != 0)  return data_->write(f);
    return std::string();
  }

  template<class T>
  struct cast {
    T item;
    cast(object& x) : item(dynamic_cast<T>(x.data())) { }
    operator T() { return item; }
  };
};

Then I use

  class my_parser {
  public:
    object item;
  };

It is then easy to add new items to the hierarchy by deriving from
object_root. Derived classes should have a clone operator (polymorphic copy
constructor)
  class A : public virtual object_root {
  public:
    virtual object_root* clone() const { return A(*this); }
  };
if one should be able to clone them, i.e., invoke the static copy
constructor to make an independent copy:
  object_root* op = new A(); // make an object
  ...
  object_root* pp = cp->clone(); // make a new copy of whatever op is.

However, I have had no use of such a clone, probably because of the
combination of the reference count and the fact that the objects in my
parser need not to be rebuilt.

The reference count avoids unnecessary copying. Because it is wrapped up in
the class object, which is used by the parser, I do not have to worry about
what happens when the parsing is interrupted (by errors, signals or
exceptions), as C++ knows how to rewind the stack, and call the destructors
appropriately.

There are many variations of the reference count. If, for example, the
objects should be able to self-mutate to another type, then the class
object should point at a class handle with the reference count, which then
points at the object_root object. However I have had no use of it in the
parser, again, because it merely builds an object which it does not attempt
to modify any of its components.

When one is using a reference count, if the object should mutate, one must
first detach it form the reference cluster, or one will modify the whole
cluster. But again, apart form the simple assignment, I have had no use for
such mutations in my parser.

One advantage with this approach that it is easy to build complicated
objects. Say if a want lists, then I can add
  sequence : public virtual {
    std::list<object> items
  ...
  };
Then any of the objects formerly added to the hierarchy can be in that list.

Such dynamic allocations are slow, but I can quickly in terms of programmer
time build complicated objects.

>> Well, it must be sufficiently general, so that it suits _everybodys_ needs.
>> This is some of the things Akim has been working on, removing restraints on
>> the code, so that one can have more general arguments, etc.
>
>I fully agree with this, but still, it can't be that hard.

Well, the only thing is to give it a try, and see how it works out.

>> I am not at the GNU helms. Most seems to be happy to compile the .tab.c
>> file as C++ (I do), which is perhaps one reason it takes so long getting
>> C++ classes written.

>That's what I did first too, but that's merely working around the
>problem of not being able to generate parser objects.

One can easily design beautiful C++ syntaxes with this "compile C as C++"
approach. Here is syntax that I use:
  Parser parser;
  Value value
  try {
    std::cin >> parser >> value;
  catch (...) { ... }

That is, the parser is implemented as a manipulator. -- One probably has to
throw exceptions in this approach, in order to properly catch errors.
Without exceptions, one might use the syntax
  if (!(std::cin >> parser >> value))  generate_error();

-- Just flip the yyparse function into your own favorite class wrap.

  Hans Aberg





reply via email to

[Prev in Thread] Current Thread [Next in Thread]