S-SymObj -- an easy way to create symbol-tables and objects.
use diagnostics -verbose;
use strict;
use warnings;
# You need to require it in a BEGIN{}..; $Debug may be one of 0/1/2
BEGIN{ require SymObj; $SymObj::Debug = 2 }
# Accessor subs return references for hashes and arrays (but shallow
# clones in wantarray context), scalars are returned "as-is"
{package X1;
SymObj::sym_create(SymObj::NONE, { # (NONE is 0..)
_name => '', _array => [qw(Is Easy)],
_hash => {To => 'hv1', Use => 'hv2'},
boing => undef}) # <- $SymObj::Debug will complain! FAILS!
}
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 = ('X1');
SymObj::sym_create(0, {}) # <- adds no fields on its own
}
# (Clean hierarchy has optimized constructor which is used, then)
if($SymObj::Debug != 0){
$o = X2->new(name => 'It detects some misuses (if $Debug > 0)',
'un' => 'known argument catched')
}else{
$o = X2->new(name => 'It detects some misuses (if $Debug > 0)')
}
print $o->name, "\n";
# Fields which mirror fieldnames of superclasses define overrides.
{package X3;
our @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 = ('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)
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, and the desired accessors and a constructor subroutine will be automatically generated. Subroutines which deal with arrays and hashes implement a feed in and forget approach, in that hash and array arguments are unfolded; the constructor will behave this way for all managed array and hash fields.
If debug was enabled upon symbol-table creation time a verbose and error checking constructor is used. Otherwise a straight constructor without any checking will be created; and if the managed object is the head of a "clean" object tree, i.e., one that is entirely managed by S-SymObj, then 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 to function.
The SymObj module is available on CPAN. The S-SymObj project is located at https://www.sdaoden.eu/code.html. It is developed using a git(1) repository, which is located at https://git.sdaoden.eu/scm/s-symobj.git
(browse it at https://git.sdaoden.eu/browse/s-symobj.git
).
$VERSION
(string, i.e., '0.8.2')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 than 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 classes and class-objects that already have been 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.
DEEP_CLONE
Bit-flag for "sym_create". If this is set then values from the per-class reference hash are deep cloned upon object construction time. Normally these fields are reference-copied except for the first, immediate level, which is shallow cloned; i.e., if the value is an array or hash, that very array or hash is cloned, but the values within it are only reference copied. But if this flag is set then array and hash members are recursively cloned in addition. E.g.:
SymObj::sym_create(SymObj::NONE, {array => [1, [2, 3]]});
->
$o = XXX->new;
die unless $o->array->[1]->[0] == 2;
XXX::array()->[0] = -1;
die unless $o->array->[0] == 1;
XXX::array()->[1]->[0] = -2;
die unless $o->array->[1]->[0] == -2;
SymObj::sym_create(SymObj::DEEP_CLONE, {array => [1, [2, 3]]});
->
$o = XXX->new;
die unless $o->array->[1]->[0] == 2;
XXX::array()->[0] = -1;
die unless $o->array->[0] == 1;
XXX::array()->[1]->[0] = -2;
die unless $o->array->[1]->[0] == 2
This flag will be overwritten by subclasses, i.e., the DEEP_CLONE
state of the actually instantiated subclass is what is used to decide whether deep cloning shall be performed or not.
pack_exists($1
=string=package/class)
Check whether 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 "DEEP_CLONE". 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, though.
$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
). This is actually a requirement that is checked if debug is enabled, since SymObj won't function properly for this field otherwise.
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 shallow clones 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 to 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 once the field is accessed first.
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 the accessor subroutinge 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 to 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
, one specifies 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.
clone_ref($1=ref, $2=boolean=deep-clone-reference)
Calling this support sub clones the variable $1
, which is treated as a plain scalar unless it is a HASH or ARRAY reference. If $2
is not false then clone_ref()
recurses for the variable, cloning all levels until no more HASH or ARRAY references remain (uncloned). The final perl variable is returned. (This is the logic behind DEEP_CLONE
.)
For completeness sake, 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.
Fixed false spelling, updated URLs, new source-code style. No functional change (since v0.8.0, 2012-12-17).
Only housekeeping updates (URLs, copyright, etc).
Support for "DEEP_CLONE" was added. The code has been reordered. Much lesser generated code is injected into classes.
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 I.
Copyright (c) 2010 - 2016 Steffen (Daode) Nurpmeso. All rights reserved under the terms of the ISC license.
Copyright (c) 1997 - 2024, Steffen Nurpmeso <[email protected]>
@(#)code-symobj.html-w42 1.18 2021-01-16T00:24:27+0000