The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

S-SymObj -- an easy way to create symbol-tables and objects.

SYNOPSIS

   # You need to require it in a BEGIN{}..; try out $Debug= 1/2
   BEGIN { require SymObj; $SymObj::Debug = 0; }

   # Accessor subs return references for hashes and arrays (but shallow
   # copy in wantarray context) scalars are returned "as-is"
   {package X1;
      BEGIN {
         SymObj::sym_create(SymObj::NONE, { # (NONE is 0..)
               _name => '', _array => [qw(Is Easy)],
               _hash => {To => 'hv1', Use => 'hv2'} });
      }
   }
   my $o = X1->new(name => 'SymObj');
   print $o->name, ' ';
   print join(' ', @{$o->array}), ' ';
   print join(' ', keys %{$o->hash}), "\n";

   # Unknown arguments are detected when DEBUG/VERBOSE is enabled.
   {package X2;
      our (@ISA); BEGIN { @ISA = ('X1');
         SymObj::sym_create(0, {}); # <- adds no fields on its own
      }
   }
   my $o = X2->new(name => 'It detects some misuses', 'un' => 'known');
   print $o->name, "\n";

   # Fields which mirror fieldnames of superclasses define overrides.
   {package X3;
      our (@ISA); BEGIN { @ISA = ('X2');
         SymObj::sym_create(0, { '_name' => 'Auto superclass-ovw'},
            sub { my $self = shift; print "X3 usr ctor\n"; });
      }
   }
   $o = X3->new();
   print $o->name, "\n";

   # One may enforce creation of array/hash accessors even for undef
   # values by using the @/% type modifiers; the objects themselves
   # are lazy-created as necessary, then...
   {package X4;
      our (@ISA); BEGIN { @ISA = ('X3');
         SymObj::sym_create(0, { '%_hash2'=>undef, '@_array2'=>undef });
      }
      sub __ctor { my $self = shift; print "X4 usr ctor\n"; }
   }
   $o = X4->new(name => 'A X4');
   die 'Lazy-allocation failed'
      if ! defined $o->hash2 || ! defined $o->array2;
   print join(' ', keys %{$o->hash2(Allocation=>1, Lazy=>1)}), ' ';
   print join(' ', @{$o->array2(qw(Can Be Used))}), "\n";

   %{$o->hash2} = ();
   $o->hash2(HashAndArray => 'easy');
   $o->hash2(qw(Accessors development));
   $o->hash2('Really', 'is');
   $o->hash2(['Swallow', 'possible']);
   $o->hash2({ Anything => 'here' });
   print join(' ', keys %{$o->hash2}), "\n";
   # P.S.: this is also true for the constructor(s)

DESCRIPTION

SymObj.pm provides an easy way to create and construct symbol-tables and objects. With a simple hash one defines the fields an object should have. An automatically instantiated constructor can then be used to create the object, and the generated accessor subs implement a feed in and forget approach when they are about to manage arrays or hashes, trying to handle all kinds of arguments; this is also true for the constructor.

If debug was enabled upon creation time a constructor which does a lot of argument checking and more is used, which is pretty useful in times when the interface is unstable. Otherwise a different constructor is used which implements no checking at all; and if the object in question is the head of a "clean" object tree, one that is entirely managed by S-SymObj, then indeed a super-fast super-lean constructor implementation is used that'll rock your house.

SymObj.pm works for Multiple-Inheritance as good as perl(1) allows. (That is to say that only one straight object tree can be used, further trees of @ISA need to be joined into the $self hash and thus loose their $self along the way, of course.) It should integrate neatlessly into SMP in respect to objects; package "static-data" however is not protected. Note that S-SymObj does not add tweaks to the perl(1) object mechanism in respect to superclasses that occur multiple times in the @ISA of some class; this is because the resulting behaviour would differ for clean S-SymObj managed and mixed trees, as well as for debug and non-debug mode (though that could be managed, actually).

Note that it is not possible to use an object tree with mixed S-SymObj managed and non-managed classes in mixed order, as in MANAGED subclassof NON-MANAGED subclassof MANAGED, because tree traversal actually stops once a NON-MANAGED class is seen. This is logical, because non-managed classes do not contain the necessary information for S-SymObj.

The SymObj module is available on CPAN. The S-SymObj project is located at http://sdaoden.users.sourceforge.net/code.html. It is developed using a git(1) repository, which is located at git.code.sf.net/p/ssymobj/code (or browse it at http://sourceforge.net/p/ssymobj/code/).

INTERFACE

$VERSION (string, i.e., '0.6.1')

A version string.

$COPYRIGHT (string)

A multiline string which contains a summary of the copyright license. S-SymObj is provided under the terms of the ISC license.

$MsgFH (default: *STDERR)

This is the file handle where all debug and verbose messages will be written to.

$Debug (0=off, 1=on, 2=verbose; default: 1)

If set to a value different to 0 then a lot of debug checks are performed, and a rather slow object-constructor path is chosen (see below). If set to a value greater than 1 then message verbosity is increased. All messages go to "MsgFH".

Note: changing this value later on will neither affect the object-constructor paths nor the per-object settings of all classes and class-objects that have already been created/instantiated.

NONE

Flag for "sym_create", value 0.

DEBUG

Bit-flag for "sym_create", meaning to enable debug on a per-object level. Cannot be used to overwrite an enabled "Debug", but will be recognized otherwise.

VERBOSE

Bit-flag for "sym_create", meaning to enable verbosity on a per-object level. Cannot be used to overwrite an enabled "Debug", but will be recognized otherwise.

pack_exists($1=string=package/class)

Check wether the class (package) $1 exists.

sym_dump($1=string OR object=symbol table target)

Dump the symbol table entries of the package or object $1.

obj_dump($1=$self)

This is in fact a wrapper around Dumper::dump.

sym_create($1=int=flags, $2=hash-ref=fields[, $3=code-ref/string])

Create accessor methods/functions in the class package from within which this function is called (best from within a BEGIN{} block) for all keys of $2, after inspecting and adjusting them for access modifiers, and do the "magic" S-SymObj symbol housekeeping, too. $2 may be the empty anonymous hash if the class does not introduce fields on its own. Note that a reference to $2 is internally stored as "$_SymObj_FIELDS" and used for the time being! It should thus be assumed that ownership of $2 is overtaken by S-SymObj.

$1 can be used to set per-class (package that is) flags, like "DEBUG" or "VERBOSE". Flags set like that will be inherited by all subclasses (unless otherwise noted). It is not possible to lower the global "Debug" state on this basis.

$3 is the optional per-object user constructor, which can be used to perform additional setup of $self as necessary. These user constructors take two arguments, $self, the newly created object, and $pkg, the class name of the actual (sub)class that is instantiated. (Well, maybe partially created up to some point in @ISA.) The user constructor doesn't have a return value.

If $3 is used, it must either be a code-reference or a string. In the latter case S-SymObj will try to locate a method of the given name once the first object of the managed type is created, and set this to be the user constructor. If $3 is not used, S-SymObj will look for a method named __ctor once the first object of the managed type is created. Note that the string and auto-search cases are not thread-protected and may thus introduce races in multithreaded programs. If in doubt, pass a code-reference.

SymObj generally "enforces" privacy (by definition) via an underscore prefix: all keys of $2 are expected to start with an underscore, but the public accessor methods will miss that (_data becomes data).

The created accessor subs work as methods if the first argument that is seen is a reference that seems to be a class instantiation (i.e., $self, as in $self->name()) and as functions otherwise (SomePack::name()), in which case the provided package template hash ($2) is used! (Note that no locking is performed in the latter case, i.e., this should not be done in multithreaded programs.) If they act upon arrays or hashes they'll return references to the members by default, but do return copies in wantarray context instead.

If a key in $2 is prefixed with an AT or a PERCENT, as in '@_name' or '%_name', respectively, then the field in question is assumed to be an array or hash, respectively. By default S-SymObj uses the value to figure out which kind of accessor has to be used, but for that the value must be set to a value different than undef, which is sometimes not desirable, e.g., when a field should be lazy allocated, only if it is really used. Note that the generic accessors will automatically instantiate an empty array/hash as necessary in these cases.

After the (optional) @ or % type modifier, one may use (also optionally) ? or !, mutually exclusive, as an access modifier. If a question mark is seen, as in '?_name', then this means that no accessor subs will be created for name. Just likewise for exclamation mark, as in '!_name', there will only be a readonly accessor sub available. Write access will be actively rejected in this case.

Whatever type and/or access modifier(s) was/were present, they will be stripped from the field name, just like a following underscore will, i.e., a field '@!_name' will actually end up as _name in the class-static hash, with an accessor sub named name.

In addition to those accessor subs there will always be private accessor subs be created which use the public name prefixed with two underscores, as in $self->__name(). These subs do nothing except returning a reference to the field in question. They're ment to be used instead of direct access of members in some contexts, i.e., for encapsulation purposes. Note that they do not automatically instantiate lazy allocated fields.

Note that, if any of the superclasses of $1, as detected through its @ISA array, provides fields which have identical names to what is provided in $2, this situation is treated as an overwrite request, and it is verified that the value type matches. Unfortunately the subclass will create accessor subs on its own, because users need to be able to adjust the class-static data. And, also unfortunately, different access policies won't be detected.

INJECTED SYMBOLS

For completeness, here is a list of all the symbols that S-SymObj creates internally for each managed package.

$_SymObj_PACKAGE

The __PACKAGE__.

$_SymObj_ISA

A copy of the class's @ISA, including the class itself. This indeed is the entire unfolded class tree, unfolded in down-top, left-right (a.k.a construction) order.

$_SymObj_ALL_CTOR_ARGS

A hash that includes all arguments that the constructor is allowed to take. (Won't cover classes that are not managed by S-SymObj.)

$_SymObj_CTOR_OVERRIDES

A list of all fields that this package overrides from superclasses. (Won't cover classes that are not managed by S-SymObj.)

$_SymObj_FIELDS

The reference to the field hash, as given to "sym_create" (modified to not include field-access modifier characters).

$_SymObj_FLAGS

Some flags. :)

$_SymObj_USR_CTOR

An optional field that holds a reference to the users constructor (once resolved).

new()

The auto-generated public class constructor.

_SymObj_ArraySet()

Shared array handler, only if needed.

_SymObj_HashSet()

Shared hash handler, only if needed.

FUTURE DIRECTIONS

Optional (flag driven) thread safety for static data access. Thread safety for resolving the user-constructor (maybe).

Maybe add support for class members, but the problem here is of course the default-argument nature of S-SymObj; we could however require initialization of the member with a package, callback tuple to (1) test references of given objects (via perl(1) UNIVERSAL, then) and initialize default objects if none has been given by user.

Finally: realize that perl(1) ships with struct and class and similar things which head in the very same direction as S-SymObj. I want to point out that i wrote this package the hard way. It is me.

LICENSE

Copyright (c) 2010 - 2012 Steffen "Daode" Nurpmeso. All rights reserved under the terms of the ISC license.